Browse Source

Merge branch 'nocodb:develop' into develop

pull/5531/head
nith2001 2 years ago committed by GitHub
parent
commit
4f27ce024e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/components/cell/Currency.vue
  2. 2
      packages/nc-gui/components/cell/DateTimePicker.vue
  3. 4
      packages/nc-gui/components/cell/Decimal.vue
  4. 4
      packages/nc-gui/components/cell/Duration.vue
  5. 4
      packages/nc-gui/components/cell/Email.vue
  6. 4
      packages/nc-gui/components/cell/Float.vue
  7. 4
      packages/nc-gui/components/cell/Integer.vue
  8. 4
      packages/nc-gui/components/cell/Percent.vue
  9. 4
      packages/nc-gui/components/cell/Text.vue
  10. 4
      packages/nc-gui/components/cell/TextArea.vue
  11. 4
      packages/nc-gui/components/cell/Url.vue
  12. 29
      packages/nc-gui/components/dashboard/TreeView.vue
  13. 3
      packages/nc-gui/components/dlg/TableCreate.vue
  14. 4
      packages/nc-gui/components/general/EmojiIcons.vue
  15. 2
      packages/nc-gui/components/smartsheet/Form.vue
  16. 29
      packages/nc-gui/components/smartsheet/Grid.vue
  17. 1
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  18. 1
      packages/nc-gui/composables/useMultiSelect/index.ts
  19. 80
      packages/nc-gui/lang/tr.json
  20. 2
      packages/nc-gui/package-lock.json
  21. 4
      packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue
  22. 2
      packages/nc-lib-gui/package.json
  23. 4
      packages/nocodb-sdk/package-lock.json
  24. 2
      packages/nocodb-sdk/package.json
  25. 2
      packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
  26. 2
      packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts
  27. 2
      packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts
  28. 2
      packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts
  29. 2
      packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts
  30. 20
      packages/nocodb/package-lock.json
  31. 4
      packages/nocodb/package.json
  32. 4
      packages/nocodb/src/controllers/caches.controller.ts
  33. 81
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  34. 49
      tests/playwright/tests/db/filters.spec.ts

4
packages/nc-gui/components/cell/Currency.vue

@ -53,9 +53,9 @@ const currency = computed(() => {
} }
}) })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
const submitCurrency = () => { const submitCurrency = () => {
if (lastSaved.value !== vModel.value) { if (lastSaved.value !== vModel.value) {

2
packages/nc-gui/components/cell/DateTimePicker.vue

@ -18,7 +18,7 @@ import {
interface Props { interface Props {
modelValue?: string | null modelValue?: string | null
isPk?: boolean isPk?: boolean
isUpdatedFromCopyNPaste: Record<string, boolean> isUpdatedFromCopyNPaste?: Record<string, boolean>
} }
const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps<Props>() const { modelValue, isPk, isUpdatedFromCopyNPaste } = defineProps<Props>()

4
packages/nc-gui/components/cell/Decimal.vue

@ -36,9 +36,9 @@ const vModel = computed({
}, },
}) })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/Duration.vue

@ -74,9 +74,9 @@ const submitDuration = () => {
isEdited.value = false isEdited.value = false
} }
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/Email.vue

@ -35,9 +35,9 @@ const vModel = computed({
const validEmail = computed(() => vModel.value && validateEmail(vModel.value)) const validEmail = computed(() => vModel.value && validateEmail(vModel.value))
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
watch( watch(
() => editEnabled.value, () => editEnabled.value,

4
packages/nc-gui/components/cell/Float.vue

@ -36,9 +36,9 @@ const vModel = computed({
}, },
}) })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/Integer.vue

@ -36,9 +36,9 @@ const vModel = computed({
}, },
}) })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
function onKeyDown(evt: KeyboardEvent) { function onKeyDown(evt: KeyboardEvent) {
return evt.key === '.' && evt.preventDefault() return evt.key === '.' && evt.preventDefault()

4
packages/nc-gui/components/cell/Percent.vue

@ -27,9 +27,9 @@ const vModel = computed({
}, },
}) })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/Text.vue

@ -23,9 +23,9 @@ const readonly = inject(ReadonlyInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/TextArea.vue

@ -19,9 +19,9 @@ const { showNull } = useGlobal()
const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' }) const vModel = useVModel(props, 'modelValue', emits, { defaultValue: '' })
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLTextAreaElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLTextAreaElement)?.focus()
</script> </script>
<template> <template>

4
packages/nc-gui/components/cell/Url.vue

@ -63,9 +63,9 @@ const url = computed(() => {
const { cellUrlOptions } = useCellUrlConfig(url) const { cellUrlOptions } = useCellUrlConfig(url)
const isExpandedFormOpen = inject(IsExpandedFormOpenInj)! const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
watch( watch(
() => editEnabled.value, () => editEnabled.value,

29
packages/nc-gui/components/dashboard/TreeView.vue

@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { nextTick } from '@vue/runtime-core'
import { Icon as IconifyIcon } from '@iconify/vue'
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import type { Input } from 'ant-design-vue' import type { Input } from 'ant-design-vue'
import { Dropdown, Tooltip, message } from 'ant-design-vue' import { Dropdown, Tooltip, message } from 'ant-design-vue'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import GithubButton from 'vue-github-button' import GithubButton from 'vue-github-button'
import { Icon } from '@iconify/vue'
import type { VNodeRef } from '#imports' import type { VNodeRef } from '#imports'
import { import {
ClientType, ClientType,
@ -279,6 +280,15 @@ function openAirtableImportDialog(baseId?: string) {
} }
} }
function scrollToTable(table: TableType) {
// get the table node in the tree view using the data-id attribute
const el = document.querySelector(`.nc-tree-item[data-id="${table?.id}"]`)
// scroll to the table node if found
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
}
function openTableCreateDialog(baseId?: string) { function openTableCreateDialog(baseId?: string) {
$e('c:table:create:navdraw') $e('c:table:create:navdraw')
@ -288,6 +298,12 @@ function openTableCreateDialog(baseId?: string) {
'modelValue': isOpen, 'modelValue': isOpen,
'baseId': baseId || bases.value[0].id, 'baseId': baseId || bases.value[0].id,
'onUpdate:modelValue': closeDialog, 'onUpdate:modelValue': closeDialog,
'onCreate': (table: TableType) => {
// on new table created scroll to the table in the tree view
nextTick(() => {
scrollToTable(table)
})
},
}) })
function closeDialog() { function closeDialog() {
@ -405,6 +421,9 @@ const duplicateTable = async (table: TableType) => {
await loadTables() await loadTables()
const newTable = tables.value.find((el) => el.id === data?.result?.id) const newTable = tables.value.find((el) => el.id === data?.result?.id)
if (newTable) addTableTab(newTable) if (newTable) addTableTab(newTable)
await nextTick(() => {
scrollToTable(newTable)
})
} else if (status === JobStatus.FAILED) { } else if (status === JobStatus.FAILED) {
message.error('Failed to duplicate table') message.error('Failed to duplicate table')
await loadTables() await loadTables()
@ -716,12 +735,12 @@ const duplicateTable = async (table: TableType) => {
<div class="flex items-center" @click.stop> <div class="flex items-center" @click.stop>
<component :is="isUIAllowed('tableIconCustomisation') ? Tooltip : 'div'"> <component :is="isUIAllowed('tableIconCustomisation') ? Tooltip : 'div'">
<span v-if="table.meta?.icon" :key="table.meta?.icon" class="nc-table-icon flex items-center"> <span v-if="table.meta?.icon" :key="table.meta?.icon" class="nc-table-icon flex items-center">
<Icon <IconifyIcon
:key="table.meta?.icon" :key="table.meta?.icon"
:data-testid="`nc-icon-${table.meta?.icon}`" :data-testid="`nc-icon-${table.meta?.icon}`"
class="text-xl" class="text-xl"
:icon="table.meta?.icon" :icon="table.meta?.icon"
></Icon> ></IconifyIcon>
</span> </span>
<component <component
:is="icon(table)" :is="icon(table)"
@ -1040,12 +1059,12 @@ const duplicateTable = async (table: TableType) => {
<div class="flex items-center" @click.stop> <div class="flex items-center" @click.stop>
<component :is="isUIAllowed('tableIconCustomisation') ? Tooltip : 'div'"> <component :is="isUIAllowed('tableIconCustomisation') ? Tooltip : 'div'">
<span v-if="table.meta?.icon" :key="table.meta?.icon" class="nc-table-icon flex items-center"> <span v-if="table.meta?.icon" :key="table.meta?.icon" class="nc-table-icon flex items-center">
<Icon <IconifyIcon
:key="table.meta?.icon" :key="table.meta?.icon"
:data-testid="`nc-icon-${table.meta?.icon}`" :data-testid="`nc-icon-${table.meta?.icon}`"
class="text-xl" class="text-xl"
:icon="table.meta?.icon" :icon="table.meta?.icon"
></Icon> ></IconifyIcon>
</span> </span>
<component <component
:is="icon(table)" :is="icon(table)"

3
packages/nc-gui/components/dlg/TableCreate.vue

@ -19,7 +19,7 @@ const props = defineProps<{
baseId: string baseId: string
}>() }>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue', 'create'])
const dialogShow = useVModel(props, 'modelValue', emit) const dialogShow = useVModel(props, 'modelValue', emit)
@ -40,6 +40,7 @@ const { table, createTable, generateUniqueTitle, tables, project } = useTable(as
type: TabType.TABLE, type: TabType.TABLE,
}) })
emit('create', table)
dialogShow.value = false dialogShow.value = false
}, props.baseId) }, props.baseId)

4
packages/nc-gui/components/general/EmojiIcons.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Icon } from '@iconify/vue' import { Icon as IconifyIcon } from '@iconify/vue'
import InfiniteLoading from 'v3-infinite-loading' import InfiniteLoading from 'v3-infinite-loading'
import { emojiIcons } from '#imports' import { emojiIcons } from '#imports'
@ -48,7 +48,7 @@ const selectIcon = (icon?: string) => {
<div class="flex gap-1 flex-wrap w-full flex-shrink overflow-y-auto scrollbar-thin-dull"> <div class="flex gap-1 flex-wrap w-full flex-shrink overflow-y-auto scrollbar-thin-dull">
<div v-for="icon of filteredIcons" :key="icon" @click="selectIcon(icon)"> <div v-for="icon of filteredIcons" :key="icon" @click="selectIcon(icon)">
<span class="cursor-pointer nc-emoji-item"> <span class="cursor-pointer nc-emoji-item">
<Icon class="text-xl iconify" :icon="`emojione:${icon}`"></Icon> <IconifyIcon class="text-xl iconify" :icon="`emojione:${icon}`"></IconifyIcon>
</span> </span>
</div> </div>
<InfiniteLoading @infinite="load"><span /></InfiniteLoading> <InfiniteLoading @infinite="load"><span /></InfiniteLoading>

2
packages/nc-gui/components/smartsheet/Form.vue

@ -837,7 +837,7 @@ watch(view, (nextView) => {
<style scoped lang="scss"> <style scoped lang="scss">
.nc-editable:hover { .nc-editable:hover {
.nc-field-remove-icon { :deep(.nc-field-remove-icon) {
@apply opacity-100; @apply opacity-100;
} }
} }

29
packages/nc-gui/components/smartsheet/Grid.vue

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick } from '@vue/runtime-core'
import type { ColumnReqType, ColumnType, GridType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnReqType, ColumnType, GridType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { import {
@ -141,11 +142,20 @@ const getContainerScrollForElement = (
) => { ) => {
const childPos = el.getBoundingClientRect() const childPos = el.getBoundingClientRect()
const parentPos = container.getBoundingClientRect() const parentPos = container.getBoundingClientRect()
// provide an extra offset to show the prev/next/up/bottom cell
const extraOffset = 15
const numColWidth = container.querySelector('thead th:nth-child(1)')?.getBoundingClientRect().width ?? 0
const primaryColWidth = container.querySelector('thead th:nth-child(2)')?.getBoundingClientRect().width ?? 0
const stickyColsWidth = numColWidth + primaryColWidth
const relativePos = { const relativePos = {
top: childPos.top - parentPos.top, top: childPos.top - parentPos.top,
right: childPos.right - parentPos.right, right: childPos.right - parentPos.right,
bottom: childPos.bottom - parentPos.bottom, bottom: childPos.bottom - parentPos.bottom,
left: childPos.left - parentPos.left, left: childPos.left - parentPos.left - stickyColsWidth,
} }
const scroll = { const scroll = {
@ -159,9 +169,9 @@ const getContainerScrollForElement = (
*/ */
scroll.left = scroll.left =
relativePos.right + (offset?.right || 0) > 0 relativePos.right + (offset?.right || 0) > 0
? container.scrollLeft + relativePos.right + (offset?.right || 0) ? container.scrollLeft + relativePos.right + (offset?.right || 0) + extraOffset
: relativePos.left - (offset?.left || 0) < 0 : relativePos.left - (offset?.left || 0) < 0
? container.scrollLeft + relativePos.left - (offset?.left || 0) ? container.scrollLeft + relativePos.left - (offset?.left || 0) - extraOffset
: container.scrollLeft : container.scrollLeft
/* /*
@ -170,9 +180,9 @@ const getContainerScrollForElement = (
*/ */
scroll.top = scroll.top =
relativePos.bottom + (offset?.bottom || 0) > 0 relativePos.bottom + (offset?.bottom || 0) > 0
? container.scrollTop + relativePos.bottom + (offset?.bottom || 0) ? container.scrollTop + relativePos.bottom + (offset?.bottom || 0) + extraOffset
: relativePos.top - (offset?.top || 0) < 0 : relativePos.top - (offset?.top || 0) < 0
? container.scrollTop + relativePos.top - (offset?.top || 0) ? container.scrollTop + relativePos.top - (offset?.top || 0) - extraOffset
: container.scrollTop : container.scrollTop
return scroll return scroll
@ -342,6 +352,12 @@ function scrollToCell(row?: number | null, col?: number | null) {
const { height: headerHeight } = tableHead.value!.getBoundingClientRect() const { height: headerHeight } = tableHead.value!.getBoundingClientRect()
const tdScroll = getContainerScrollForElement(td, gridWrapper.value, { top: headerHeight, bottom: 9, right: 9 }) const tdScroll = getContainerScrollForElement(td, gridWrapper.value, { top: headerHeight, bottom: 9, right: 9 })
// if first column set left to 0 since it's sticky it will be visible and calculated value will be wrong
// setting left to 0 will make it scroll to the left
if (col === 0) {
tdScroll.left = 0
}
if (rows && row === rows.length - 2) { if (rows && row === rows.length - 2) {
// if last row make 'Add New Row' visible // if last row make 'Add New Row' visible
gridWrapper.value.scrollTo({ gridWrapper.value.scrollTo({
@ -631,6 +647,9 @@ const onNavigate = (dir: NavigateDir) => {
} }
break break
} }
nextTick(() => {
scrollToCell()
})
} }
const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) => { const showContextMenu = (e: MouseEvent, target?: { row: number; col: number }) => {

1
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -188,7 +188,6 @@ useEventListener('keydown', (e: KeyboardEvent) => {
<LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" /> <LazySmartsheetColumnQrCodeOptions v-if="formState.uidt === UITypes.QrCode" v-model="formState" />
<LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" /> <LazySmartsheetColumnBarcodeOptions v-if="formState.uidt === UITypes.Barcode" v-model="formState" />
<LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" /> <LazySmartsheetColumnCurrencyOptions v-if="formState.uidt === UITypes.Currency" v-model:value="formState" />
<LazySmartsheetColumnGeoDataOptions v-if="formState.uidt === UITypes.GeoData" v-model:value="formState" />
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" /> <LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" v-model:value="formState" />
<LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" /> <LazySmartsheetColumnRatingOptions v-if="formState.uidt === UITypes.Rating" v-model:value="formState" />
<LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" /> <LazySmartsheetColumnCheckboxOptions v-if="formState.uidt === UITypes.Checkbox" v-model:value="formState" />

1
packages/nc-gui/composables/useMultiSelect/index.ts

@ -208,6 +208,7 @@ export function useMultiSelect(
selectedRange.startRange({ row, col }) selectedRange.startRange({ row, col })
selectedRange.endRange({ row, col }) selectedRange.endRange({ row, col })
makeActive(row, col) makeActive(row, col)
scrollToActiveCell?.()
isMouseDown = false isMouseDown = false
} }

80
packages/nc-gui/lang/tr.json

@ -39,7 +39,7 @@
"signIn": "KAYIT OL", "signIn": "KAYIT OL",
"signOut": "Oturumu Kapat", "signOut": "Oturumu Kapat",
"required": "Gerekli", "required": "Gerekli",
"enableScanner": "Enable Scanner for filling", "enableScanner": "Doldurmak için tarayıcıyı etkinleştir",
"preferred": "Tercihen", "preferred": "Tercihen",
"mandatory": "Zorunlu", "mandatory": "Zorunlu",
"loading": "Yükleniyor ...", "loading": "Yükleniyor ...",
@ -76,7 +76,7 @@
"hideField": "Alanı Gizle", "hideField": "Alanı Gizle",
"sortAsc": "Artan Sırala", "sortAsc": "Artan Sırala",
"sortDesc": "Azalan Sıralama", "sortDesc": "Azalan Sıralama",
"geoDataField": "GeoData Field" "geoDataField": "GeoData Alanı"
}, },
"objects": { "objects": {
"project": "Proje", "project": "Proje",
@ -101,7 +101,7 @@
"form": "Form", "form": "Form",
"kanban": "Kanban", "kanban": "Kanban",
"calendar": "Takvim", "calendar": "Takvim",
"map": "Map" "map": "Harita"
}, },
"user": "Kullanıcı", "user": "Kullanıcı",
"users": "Kullanıcılar", "users": "Kullanıcılar",
@ -210,8 +210,8 @@
"advancedSettings": "Gelişmiş Ayarlar", "advancedSettings": "Gelişmiş Ayarlar",
"codeSnippet": "Kod Parçacığı", "codeSnippet": "Kod Parçacığı",
"keyboardShortcut": "Klavye Kısayolları", "keyboardShortcut": "Klavye Kısayolları",
"generateRandomName": "Generate Random Name", "generateRandomName": "Rastgele Ad Oluştur",
"findRowByScanningCode": "Find row by scanning a QR or Barcode" "findRowByScanningCode": "QR veya Barkod okutarak satır bulun"
}, },
"labels": { "labels": {
"createdBy": "Tarafından Oluşturuldu", "createdBy": "Tarafından Oluşturuldu",
@ -221,7 +221,7 @@
"viewName": "Görünüm adı", "viewName": "Görünüm adı",
"viewLink": "Görünüm Linki", "viewLink": "Görünüm Linki",
"columnName": "Sütun Adı", "columnName": "Sütun Adı",
"columnToScanFor": "Column to scan", "columnToScanFor": "Taranacak sütun",
"columnType": "Sütun Tipi", "columnType": "Sütun Tipi",
"roleName": "Rol Adı", "roleName": "Rol Adı",
"roleDescription": "Rol Tanımı", "roleDescription": "Rol Tanımı",
@ -238,7 +238,7 @@
"action": "Aksiyon", "action": "Aksiyon",
"actions": "Aksiyonlar", "actions": "Aksiyonlar",
"operation": "İşlem", "operation": "İşlem",
"operationSub": "Sub Operation", "operationSub": "Alt İşlem",
"operationType": "İşlem türü", "operationType": "İşlem türü",
"operationSubType": "İşlem alt-türü", "operationSubType": "İşlem alt-türü",
"description": "Tanım", "description": "Tanım",
@ -260,9 +260,9 @@
"barcodeFormat": "Barkod formatı", "barcodeFormat": "Barkod formatı",
"qrCodeValueTooLong": "QR kodu için çok fazla karakter", "qrCodeValueTooLong": "QR kodu için çok fazla karakter",
"barcodeValueTooLong": "Barkod için çok fazla karakter", "barcodeValueTooLong": "Barkod için çok fazla karakter",
"currentLocation": "Current Location", "currentLocation": "Geçerli Konum",
"lng": "Lng", "lng": "Boylam",
"lat": "Lat", "lat": "Enlem",
"aggregateFunction": "Birleştirme fonksiyonu", "aggregateFunction": "Birleştirme fonksiyonu",
"dbCreateIfNotExists": "Veritabanı : yoksa oluştur", "dbCreateIfNotExists": "Veritabanı : yoksa oluştur",
"clientKey": "İstemci Anahtarı", "clientKey": "İstemci Anahtarı",
@ -385,18 +385,18 @@
"nextRecord": "Sonraki kayıt", "nextRecord": "Sonraki kayıt",
"previousRecord": "Önceki kayıt", "previousRecord": "Önceki kayıt",
"copyApiURL": "API linkini kopyala", "copyApiURL": "API linkini kopyala",
"createTable": "Create New Table", "createTable": "Yeni tablo oluştur",
"refreshTable": "Tabloları Yenile", "refreshTable": "Tabloları Yenile",
"renameTable": "Rename Table", "renameTable": "Tabloyu Yeniden Adlandır",
"deleteTable": "Delete Table", "deleteTable": "Tabloyu Sil",
"addField": "Tabloya yeni alan ekle", "addField": "Tabloya yeni alan ekle",
"setDisplay": "Set as Display value", "setDisplay": "Görünüm değeri olarak ayarla",
"addRow": "Yeni satır ekle", "addRow": "Yeni satır ekle",
"saveRow": "Satırı kaydet", "saveRow": "Satırı kaydet",
"saveAndExit": "Kaydet ve Çık", "saveAndExit": "Kaydet ve Çık",
"saveAndStay": "Kaydet ve Kal", "saveAndStay": "Kaydet ve Kal",
"insertRow": "Yeni Satır Ekle", "insertRow": "Yeni Satır Ekle",
"duplicateRow": "Duplicate Row", "duplicateRow": "Satırı Çoğalt",
"deleteRow": "Satırı Sil", "deleteRow": "Satırı Sil",
"deleteSelectedRow": "Seçilen Satırları Sil", "deleteSelectedRow": "Seçilen Satırları Sil",
"importExcel": "Excel içe aktar", "importExcel": "Excel içe aktar",
@ -412,8 +412,8 @@
"changePwd": "Şifre değiştir", "changePwd": "Şifre değiştir",
"createView": "Bir görünüm oluştur", "createView": "Bir görünüm oluştur",
"shareView": "Görünümü paylaş", "shareView": "Görünümü paylaş",
"findRowByCodeScan": "Find row by scan", "findRowByCodeScan": "Tarayarak satır bul",
"fillByCodeScan": "Fill by scan", "fillByCodeScan": "Tarayarak doldur",
"listSharedView": "Paylaşılan Görünümler", "listSharedView": "Paylaşılan Görünümler",
"ListView": "Görünüm Listesi", "ListView": "Görünüm Listesi",
"copyView": "Görünümü kopyala", "copyView": "Görünümü kopyala",
@ -429,10 +429,10 @@
"openTab": "Yeni sekme aç", "openTab": "Yeni sekme aç",
"iFrame": "Gömülü HTML kodunu kopyalayın", "iFrame": "Gömülü HTML kodunu kopyalayın",
"addWebhook": "Yeni Webhook ekle", "addWebhook": "Yeni Webhook ekle",
"enableWebhook": "Enable Webhook", "enableWebhook": "Webhook'u Etkinleştir",
"testWebhook": "Test Webhook", "testWebhook": "Webhook'u Test Et",
"copyWebhook": "Copy Webhook", "copyWebhook": "Webhook'u Kopyala",
"deleteWebhook": "Delete Webhook", "deleteWebhook": "Webhook'u Sil",
"newToken": "Yeni Token ekle", "newToken": "Yeni Token ekle",
"exportZip": "Zip olarak dışa aktar", "exportZip": "Zip olarak dışa aktar",
"importZip": "Zip olarak içe aktar", "importZip": "Zip olarak içe aktar",
@ -472,10 +472,10 @@
"map": { "map": {
"mappedBy": "Mapped By", "mappedBy": "Mapped By",
"chooseMappingField": "Choose a Mapping Field", "chooseMappingField": "Choose a Mapping Field",
"openInGoogleMaps": "Google Maps", "openInGoogleMaps": "Google Haritalar",
"openInOpenStreetMap": "OSM" "openInOpenStreetMap": "OSM"
}, },
"toggleMobileMode": "Toggle Mobile Mode" "toggleMobileMode": "Mobil Modu Aç / Kapat"
}, },
"tooltip": { "tooltip": {
"saveChanges": "Değişiklikleri Kaydet", "saveChanges": "Değişiklikleri Kaydet",
@ -542,15 +542,15 @@
"orgViewer": "İzleyicinin yeni proje oluşturmasına izin verilmez ancak davet edilen herhangi bir projeye erişebilir." "orgViewer": "İzleyicinin yeni proje oluşturmasına izin verilmez ancak davet edilen herhangi bir projeye erişebilir."
}, },
"codeScanner": { "codeScanner": {
"loadingScanner": "Loading the scanner...", "loadingScanner": "Tarayıcı yükleniyor...",
"selectColumn": "Select a column (QR code or Barcode) that you want to use for finding a row by scanning.", "selectColumn": "Tarayarak satır bulmak için kullanmak istediğiniz sütunu (QR kodu veya Barkod) seçin.",
"moreThanOneRowFoundForCode": "More than one row found for this code. Currently only unique codes are supported.", "moreThanOneRowFoundForCode": "Bu kod için birden fazla satır bulundu. Şu anda yalnızca benzersiz kodlar desteklenmektedir.",
"noRowFoundForCode": "No row found for this code for the selected column" "noRowFoundForCode": "Seçilen sütunda bu kod için satır bulunamadı"
}, },
"map": { "map": {
"overLimit": "You're over the limit.", "overLimit": "Sınırı aştınız.",
"closeLimit": "You're getting close to the limit.", "closeLimit": "Sınıra yaklaşıyorsunuz.",
"limitNumber": "The limit of markers shown in a Map View is 1000 records." "limitNumber": "Bir Harita Görünümünde en fazla 1000 kayıt gösterilebilir."
}, },
"footerInfo": "Sayfa başına satır", "footerInfo": "Sayfa başına satır",
"upload": "Yüklenecek Dosyayı Seçin", "upload": "Yüklenecek Dosyayı Seçin",
@ -634,7 +634,7 @@
"gallery": "Galeri görünümü ekle", "gallery": "Galeri görünümü ekle",
"form": "Form görünümü ekle", "form": "Form görünümü ekle",
"kanban": "Kanban görünümü ekle", "kanban": "Kanban görünümü ekle",
"map": "Add Map View", "map": "Harita Görünümü Ekle",
"calendar": "Takvim görünümü ekle" "calendar": "Takvim görünümü ekle"
}, },
"tablesMetadataInSync": "Tablonun meta verileri senkronize", "tablesMetadataInSync": "Tablonun meta verileri senkronize",
@ -666,11 +666,11 @@
"deleteViewConfirmation": "Bu görünümü silmek istediğinizden emin misiniz?", "deleteViewConfirmation": "Bu görünümü silmek istediğinizden emin misiniz?",
"deleteTableConfirmation": "Tabloyu silmek istiyor musunuz", "deleteTableConfirmation": "Tabloyu silmek istiyor musunuz",
"showM2mTables": "M2M Tablolarını Göster", "showM2mTables": "M2M Tablolarını Göster",
"showM2mTablesDesc": "Many-to-many relation is supported via a junction table & is hidden by default. Enable this option to list all such tables along with existing tables.", "showM2mTablesDesc": "Çoktan çoğa ilişki bir köprü tablosu aracılığıyla oluşturulur ve bu tablo varsayılan olarak gizlenir. Mevcut tablolarla birlikte bu köprü tablolarını da listelemek için bu seçeneği etkinleştirin.",
"showNullInCells": "Show NULL in Cells", "showNullInCells": "Hücrelerde NULL Göster",
"showNullInCellsDesc": "Display 'NULL' tag in cells holding NULL value. This helps differentiate against cells holding EMPTY string.", "showNullInCellsDesc": "NULL değerini taşıyan hücreler için 'NULL' etiketini göster. Bu BOŞ dizi değerleri ile ayırt etmenize yardımcı olur.",
"showNullAndEmptyInFilter": "Show NULL and EMPTY in Filter", "showNullAndEmptyInFilter": "Filtrelerde NULL ve EMPTY Göster",
"showNullAndEmptyInFilterDesc": "Enable 'additional' filters to differentiate fields containing NULL & Empty Strings. Default support for Blank treats both NULL & Empty strings alike.", "showNullAndEmptyInFilterDesc": "NULL ve Boş dizileri ayırt etmek için 'Ekstra' filtreleri etkinleştir. Varsayılan 'Blank' değeri NULL ve boş dizileri aynı şekilde ele alır.",
"deleteKanbanStackConfirmation": "Bu yığının silinmesi `{stackToBeDeleted}` seçim seçeneğini `{groupingField}` adresinden de kaldıracaktır. Kayıtlar kategorize edilmemiş yığına taşınacaktır.", "deleteKanbanStackConfirmation": "Bu yığının silinmesi `{stackToBeDeleted}` seçim seçeneğini `{groupingField}` adresinden de kaldıracaktır. Kayıtlar kategorize edilmemiş yığına taşınacaktır.",
"computedFieldEditWarning": "Hesaplanan alan: içerik salt okunurdur. Yeniden yapılandırmak için sütun düzenleme menüsünü kullanın", "computedFieldEditWarning": "Hesaplanan alan: içerik salt okunurdur. Yeniden yapılandırmak için sütun düzenleme menüsünü kullanın",
"computedFieldDeleteWarning": "Hesaplanan alan: içerik salt okunurdur. İçerik temizlenemiyor.", "computedFieldDeleteWarning": "Hesaplanan alan: içerik salt okunurdur. İçerik temizlenemiyor.",
@ -698,7 +698,7 @@
"allowedSpecialCharList": "İzin verilen özel karakter listesi" "allowedSpecialCharList": "İzin verilen özel karakter listesi"
}, },
"invalidURL": "Geçersiz URL", "invalidURL": "Geçersiz URL",
"invalidEmail": "Invalid Email", "invalidEmail": "Geçersiz E-posta",
"internalError": "Bazı dahili hatalar oluştu", "internalError": "Bazı dahili hatalar oluştu",
"templateGeneratorNotFound": "Şablon Oluşturucu bulunamıyor!", "templateGeneratorNotFound": "Şablon Oluşturucu bulunamıyor!",
"fileUploadFailed": "Dosya yüklenemedi", "fileUploadFailed": "Dosya yüklenemedi",
@ -726,7 +726,7 @@
"nameShouldStartWithAnAlphabetOr_": "İsim bir alfabe veya _ ile başlamalıdır", "nameShouldStartWithAnAlphabetOr_": "İsim bir alfabe veya _ ile başlamalıdır",
"followingCharactersAreNotAllowed": "Aşağıdaki karakterlere izin verilmez", "followingCharactersAreNotAllowed": "Aşağıdaki karakterlere izin verilmez",
"columnNameRequired": "Sütun adı gereklidir", "columnNameRequired": "Sütun adı gereklidir",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "Sütun adının uzunluğu maksimum {value} karakter sınırını aşıyor",
"projectNameExceeds50Characters": "Proje adı 50 karakteri aşıyor", "projectNameExceeds50Characters": "Proje adı 50 karakteri aşıyor",
"projectNameCannotStartWithSpace": "Proje adı boşlukla başlayamaz", "projectNameCannotStartWithSpace": "Proje adı boşlukla başlayamaz",
"requiredField": "Zorunlu alan", "requiredField": "Zorunlu alan",
@ -759,7 +759,7 @@
}, },
"success": { "success": {
"columnDuplicated": "Sütun başarıyla çoğaltıldı", "columnDuplicated": "Sütun başarıyla çoğaltıldı",
"rowDuplicatedWithoutSavedYet": "Row duplicated (not saved)", "rowDuplicatedWithoutSavedYet": "Satır çoğaltıldı (kaydedilmedi)",
"updatedUIACL": "Tablolar için UI ACL başarıyla güncellendi", "updatedUIACL": "Tablolar için UI ACL başarıyla güncellendi",
"pluginUninstalled": "Eklenti başarıyla kaldırıldı", "pluginUninstalled": "Eklenti başarıyla kaldırıldı",
"pluginSettingsSaved": "Eklenti ayarları başarıyla kaydedildi", "pluginSettingsSaved": "Eklenti ayarları başarıyla kaydedildi",
@ -779,7 +779,7 @@
"userDeletedFromProject": "Kullanıcı projeden başarıyla silindi", "userDeletedFromProject": "Kullanıcı projeden başarıyla silindi",
"inviteEmailSent": "Davet E-postası başarıyla gönderildi", "inviteEmailSent": "Davet E-postası başarıyla gönderildi",
"inviteURLCopied": "Panoya kopyalanan davet URL'si", "inviteURLCopied": "Panoya kopyalanan davet URL'si",
"commentCopied": "Comment copied to clipboard", "commentCopied": "Yorum panoya kopyalandı",
"passwordResetURLCopied": "Panoya kopyalanan parola sıfırlama URL'si", "passwordResetURLCopied": "Panoya kopyalanan parola sıfırlama URL'si",
"shareableURLCopied": "Paylaşılabilir temel URL panoya kopyalandı!", "shareableURLCopied": "Paylaşılabilir temel URL panoya kopyalandı!",
"embeddableHTMLCodeCopied": "Yerleştirilebilir HTML kodu kopyalandı!", "embeddableHTMLCodeCopied": "Yerleştirilebilir HTML kodu kopyalandı!",

2
packages/nc-gui/package-lock.json generated

@ -110,7 +110,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.108.0", "version": "0.108.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

4
packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { Icon } from '@iconify/vue' import { Icon as IconifyIcon } from '@iconify/vue'
import type { TabItem } from '~/lib' import type { TabItem } from '~/lib'
import { TabType } from '~/lib' import { TabType } from '~/lib'
import { TabMetaInj, iconMap, provide, storeToRefs, useGlobal, useSidebar, useTabs } from '#imports' import { TabMetaInj, iconMap, provide, storeToRefs, useGlobal, useSidebar, useTabs } from '#imports'
@ -58,7 +58,7 @@ const hideSidebarOnClickOrTouchIfMobileMode = () => {
<template #tab> <template #tab>
<div class="flex items-center gap-2" data-testid="nc-tab-title"> <div class="flex items-center gap-2" data-testid="nc-tab-title">
<div class="flex items-center"> <div class="flex items-center">
<Icon <IconifyIcon
v-if="tab.meta?.icon" v-if="tab.meta?.icon"
:icon="tab.meta?.icon" :icon="tab.meta?.icon"
class="text-xl" class="text-xl"

2
packages/nc-lib-gui/package.json

@ -1,6 +1,6 @@
{ {
"name": "nc-lib-gui", "name": "nc-lib-gui",
"version": "0.108.0", "version": "0.108.1",
"description": "NocoDB GUI", "description": "NocoDB GUI",
"author": { "author": {
"name": "NocoDB", "name": "NocoDB",

4
packages/nocodb-sdk/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.108.0", "version": "0.108.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.108.0", "version": "0.108.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",

2
packages/nocodb-sdk/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb-sdk", "name": "nocodb-sdk",
"version": "0.108.0", "version": "0.108.1",
"description": "NocoDB SDK", "description": "NocoDB SDK",
"main": "build/main/index.js", "main": "build/main/index.js",
"typings": "build/main/index.d.ts", "typings": "build/main/index.d.ts",

2
packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts

@ -832,7 +832,7 @@ export class MssqlUi {
} }
static getAbstractType(col): any { static getAbstractType(col): any {
switch ((col.dt || col.dt).toLowerCase()) { switch (col.dt?.toLowerCase()) {
case 'bigint': case 'bigint':
case 'smallint': case 'smallint':
case 'bit': case 'bit':

2
packages/nocodb-sdk/src/lib/sqlUi/MysqlUi.ts

@ -871,7 +871,7 @@ export class MysqlUi {
} }
static getAbstractType(col): any { static getAbstractType(col): any {
switch (col.dt.toLowerCase()) { switch (col.dt?.toLowerCase()) {
case 'int': case 'int':
case 'smallint': case 'smallint':
case 'mediumint': case 'mediumint':

2
packages/nocodb-sdk/src/lib/sqlUi/OracleUi.ts

@ -689,7 +689,7 @@ export class OracleUi {
} }
static getAbstractType(col): any { static getAbstractType(col): any {
switch ((col.dt || col.dt).toLowerCase()) { switch (col.dt?.toLowerCase()) {
case 'integer': case 'integer':
return 'integer'; return 'integer';
case 'bfile': case 'bfile':

2
packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts

@ -1358,7 +1358,7 @@ export class PgUi {
} }
static getAbstractType(col): any { static getAbstractType(col): any {
switch ((col.dt || col.dt).toLowerCase()) { switch (col.dt?.toLowerCase()) {
case 'anyenum': case 'anyenum':
return 'enum'; return 'enum';
case 'anynonarray': case 'anynonarray':

2
packages/nocodb-sdk/src/lib/sqlUi/SnowflakeUi.ts

@ -572,7 +572,7 @@ export class SnowflakeUi {
} }
static getAbstractType(col): any { static getAbstractType(col): any {
switch (col.dt.toUpperCase()) { switch (col.dt?.toUpperCase()) {
case 'NUMBER': case 'NUMBER':
case 'DECIMAL': case 'DECIMAL':
case 'NUMERIC': case 'NUMERIC':

20
packages/nocodb/package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.108.0", "version": "0.108.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "nocodb", "name": "nocodb",
"version": "0.108.0", "version": "0.108.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"@google-cloud/storage": "^5.7.2", "@google-cloud/storage": "^5.7.2",
@ -80,7 +80,7 @@
"mysql2": "^3.2.0", "mysql2": "^3.2.0",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "^0.2.87", "nc-help": "^0.2.87",
"nc-lib-gui": "0.108.0", "nc-lib-gui": "0.108.1",
"nc-plugin": "^0.1.3", "nc-plugin": "^0.1.3",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",
@ -190,7 +190,7 @@
} }
}, },
"../nocodb-sdk": { "../nocodb-sdk": {
"version": "0.108.0", "version": "0.108.1",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
@ -13157,9 +13157,9 @@
} }
}, },
"node_modules/nc-lib-gui": { "node_modules/nc-lib-gui": {
"version": "0.108.0", "version": "0.108.1",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.108.0.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.108.1.tgz",
"integrity": "sha512-ZV4g5ivc5T5rGDZD89UqJtazNOv4Bo/mc/8sf3OXLOPB4ksIp8v63mSIeb+jBE9TRdptRKA+Ml67Rczwa+0SKA==", "integrity": "sha512-GYhDzkcqOCQ/pqWpxU8hI/4KcuuQrqkhG9zXmwUx1pQlz9qD70X8bQtjqb5rdvJw6diGdYneMl+wRI48bLiUAg==",
"dependencies": { "dependencies": {
"express": "^4.17.1" "express": "^4.17.1"
} }
@ -28442,9 +28442,9 @@
} }
}, },
"nc-lib-gui": { "nc-lib-gui": {
"version": "0.108.0", "version": "0.108.1",
"resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.108.0.tgz", "resolved": "https://registry.npmjs.org/nc-lib-gui/-/nc-lib-gui-0.108.1.tgz",
"integrity": "sha512-ZV4g5ivc5T5rGDZD89UqJtazNOv4Bo/mc/8sf3OXLOPB4ksIp8v63mSIeb+jBE9TRdptRKA+Ml67Rczwa+0SKA==", "integrity": "sha512-GYhDzkcqOCQ/pqWpxU8hI/4KcuuQrqkhG9zXmwUx1pQlz9qD70X8bQtjqb5rdvJw6diGdYneMl+wRI48bLiUAg==",
"requires": { "requires": {
"express": "^4.17.1" "express": "^4.17.1"
} }

4
packages/nocodb/package.json

@ -1,6 +1,6 @@
{ {
"name": "nocodb", "name": "nocodb",
"version": "0.108.0", "version": "0.108.1",
"description": "NocoDB Backend", "description": "NocoDB Backend",
"main": "dist/bundle.js", "main": "dist/bundle.js",
"author": { "author": {
@ -113,7 +113,7 @@
"mysql2": "^3.2.0", "mysql2": "^3.2.0",
"nanoid": "^3.1.20", "nanoid": "^3.1.20",
"nc-help": "^0.2.87", "nc-help": "^0.2.87",
"nc-lib-gui": "0.108.0", "nc-lib-gui": "0.108.1",
"nc-plugin": "^0.1.3", "nc-plugin": "^0.1.3",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"nocodb-sdk": "file:../nocodb-sdk", "nocodb-sdk": "file:../nocodb-sdk",

4
packages/nocodb/src/controllers/caches.controller.ts

@ -1,8 +1,10 @@
import { Controller, Delete, Get } from '@nestjs/common'; import { Controller, Delete, Get, UseGuards } from '@nestjs/common';
import { GlobalGuard } from '../guards/global/global.guard';
import { Acl } from '../middlewares/extract-project-id/extract-project-id.middleware'; import { Acl } from '../middlewares/extract-project-id/extract-project-id.middleware';
import { CachesService } from '../services/caches.service'; import { CachesService } from '../services/caches.service';
@Controller() @Controller()
@UseGuards(GlobalGuard)
export class CachesController { export class CachesController {
constructor(private readonly cachesService: CachesService) {} constructor(private readonly cachesService: CachesService) {}

81
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -37,6 +37,75 @@ export class ToolbarFilterPage extends BasePage {
await this.get().locator(`button:has-text("Add Filter")`).first().click(); await this.get().locator(`button:has-text("Add Filter")`).first().click();
} }
// can reuse code for addFilterGroup and addFilter
// support for subOperation & datatype specific filter operations not supported yet
async addFilterGroup({
title,
operation,
_subOperation: _subOperation,
value,
_locallySaved: _locallySaved = false,
_dataType: _dataType,
_openModal: _openModal = false,
_skipWaitingResponse: _skipWaitingResponse = false, // used for undo (single request, less stable)
filterGroupIndex = 0,
filterLogicalOperator = 'AND',
}: {
title: string;
operation: string;
_subOperation?: string; // for date datatype
value?: string;
_locallySaved?: boolean;
_dataType?: string;
_openModal?: boolean;
_skipWaitingResponse?: boolean;
filterGroupIndex?: number;
filterLogicalOperator?: string;
}) {
await this.get().locator(`button:has-text("Add Filter Group")`).last().click();
const filterDropdown = await this.get().locator('.menu-filter-dropdown').nth(filterGroupIndex);
await filterDropdown.waitFor({ state: 'visible' });
await filterDropdown.locator(`button:has-text("Add Filter")`).first().click();
const selectField = await filterDropdown.locator('.nc-filter-field-select').last();
const selectOperation = await filterDropdown.locator('.nc-filter-operation-select').last();
const selectValue = await filterDropdown.locator('.nc-filter-value-select > input').last();
await selectField.waitFor({ state: 'visible' });
await selectField.click();
const fieldDropdown = await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.last()
.locator(`div[label="${title}"]:visible`);
await fieldDropdown.waitFor({ state: 'visible' });
await fieldDropdown.click();
await selectOperation.waitFor({ state: 'visible' });
await selectOperation.click();
const operationDropdown = await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-filter-comp-op')
.last()
.locator(`.ant-select-item:has-text("${operation}")`);
await operationDropdown.waitFor({ state: 'visible' });
await operationDropdown.click();
await selectValue.waitFor({ state: 'visible' });
await selectValue.fill(value);
if (filterGroupIndex) {
if (filterLogicalOperator === 'OR') {
const logicalButton = await this.rootPage.locator('div.flex.bob').nth(filterGroupIndex - 1);
await logicalButton.waitFor({ state: 'visible' });
await logicalButton.click();
const logicalDropdown = await this.rootPage.locator(
'div.ant-select-dropdown.nc-dropdown-filter-logical-op-group'
);
await logicalDropdown.waitFor({ state: 'visible' });
await logicalDropdown.locator(`.ant-select-item:has-text("${filterLogicalOperator}")`).click();
}
}
}
async add({ async add({
title, title,
operation, operation,
@ -65,7 +134,7 @@ export class ToolbarFilterPage extends BasePage {
await this.rootPage.locator('.nc-filter-field-select').last().click(); await this.rootPage.locator('.nc-filter-field-select').last().click();
if (skipWaitingResponse) { if (skipWaitingResponse) {
this.rootPage await this.rootPage
.locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list') .locator('div.ant-select-dropdown.nc-dropdown-toolbar-field-list')
.locator(`div[label="${title}"]:visible`) .locator(`div[label="${title}"]:visible`)
.click(); .click();
@ -88,7 +157,7 @@ export class ToolbarFilterPage extends BasePage {
// first() : filter list has >, >= // first() : filter list has >, >=
if (skipWaitingResponse) { if (skipWaitingResponse) {
this.rootPage await this.rootPage
.locator('.nc-dropdown-filter-comp-op') .locator('.nc-dropdown-filter-comp-op')
.locator(`.ant-select-item:has-text("${operation}")`) .locator(`.ant-select-item:has-text("${operation}")`)
.first() .first()
@ -117,7 +186,7 @@ export class ToolbarFilterPage extends BasePage {
// first() : filter list has >, >= // first() : filter list has >, >=
if (skipWaitingResponse) { if (skipWaitingResponse) {
this.rootPage await this.rootPage
.locator('.nc-dropdown-filter-comp-sub-op') .locator('.nc-dropdown-filter-comp-sub-op')
.locator(`.ant-select-item:has-text("${subOperation}")`) .locator(`.ant-select-item:has-text("${subOperation}")`)
.first() .first()
@ -167,7 +236,7 @@ export class ToolbarFilterPage extends BasePage {
await this.rootPage.locator(`.ant-picker-dropdown:visible`); await this.rootPage.locator(`.ant-picker-dropdown:visible`);
if (skipWaitingResponse) { if (skipWaitingResponse) {
this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(); await this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click();
} else { } else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(), uiAction: () => this.rootPage.locator(`.ant-picker-cell-inner:has-text("${value}")`).click(),
@ -188,7 +257,7 @@ export class ToolbarFilterPage extends BasePage {
break; break;
case UITypes.Duration: case UITypes.Duration:
if (skipWaitingResponse) { if (skipWaitingResponse) {
this.get().locator('.nc-filter-value-select').locator('input').fill(value); await this.get().locator('.nc-filter-value-select').locator('input').fill(value);
} else { } else {
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.get().locator('.nc-filter-value-select').locator('input').fill(value), uiAction: () => this.get().locator('.nc-filter-value-select').locator('input').fill(value),
@ -298,4 +367,4 @@ export class ToolbarFilterPage extends BasePage {
return opListText; return opListText;
} }
} }

49
tests/playwright/tests/db/filters.spec.ts

@ -1339,3 +1339,52 @@ test.describe('Filter Tests: Toggle button', () => {
await dashboard.settings.toggleNullEmptyFilters(); await dashboard.settings.toggleNullEmptyFilters();
}); });
}); });
test.describe('Filter Tests: Filter groups', () => {
/**
* Steps
*
* 1. Open table
* 2. Verify filter options : should not include NULL & EMPTY options
* 3. Enable `Show NULL & EMPTY in Filter` in Project Settings
* 4. Verify filter options : should include NULL & EMPTY options
* 5. Add NULL & EMPTY filters
* 6. Disable `Show NULL & EMPTY in Filter` in Project Settings : should not be allowed
* 7. Remove the NULL & EMPTY filters
* 8. Disable `Show NULL & EMPTY in Filter` in Project Settings again : should be allowed
*
*/
test.beforeEach(async ({ page }) => {
context = await setup({ page, isEmptyProject: false });
dashboard = new DashboardPage(page, context.project);
toolbar = dashboard.grid.toolbar;
});
test('Filter: Empty filters', async () => {
await dashboard.closeTab({ title: 'Team & Auth' });
await dashboard.treeView.openTable({ title: 'Country', networkResponse: false });
await toolbar.clickFilter({ networkValidation: false });
await toolbar.filter.addFilterGroup({
title: 'Country',
operation: 'is equal',
value: 'Argentina',
});
await toolbar.clickFilter({ networkValidation: false });
await toolbar.clickFilter({ networkValidation: false });
await toolbar.filter.addFilterGroup({
title: 'Country',
operation: 'is equal',
value: 'Indonesia',
filterGroupIndex: 1,
filterLogicalOperator: 'OR',
});
await toolbar.clickFilter({ networkValidation: false });
await validateRowArray({
rowCount: 2,
});
});
});

Loading…
Cancel
Save