Browse Source

Merge branch 'develop' into nc-fix/cross-browser-ui-issue

pull/7447/head
Raju Udava 10 months ago
parent
commit
526ea9322f
  1. 1
      markdown/readme/languages/README.md
  2. 285
      markdown/readme/languages/ukrainian.md
  3. 17
      packages/nc-gui/components/shared-view/Gallery.vue
  4. 3
      packages/nc-gui/components/smartsheet/Kanban.vue
  5. 45
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  6. 11
      packages/nc-gui/composables/useExpandedFormStore.ts
  7. 46
      packages/nc-gui/lang/pl.json
  8. 4
      packages/noco-docs/docs/020.getting-started/050.self-hosted/010.installation.md
  9. 1
      packages/nocodb/src/db/BaseModelSqlv2.ts
  10. 7
      packages/nocodb/src/models/Column.ts
  11. 19
      packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts
  12. 59
      packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts
  13. 37
      packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts
  14. 2
      tests/playwright/pages/SharedForm/index.ts

1
markdown/readme/languages/README.md

@ -16,4 +16,5 @@ Supported Translations:
<li><a href="russian.md">Russian</a></li>
<li><a href="dutch.md">Dutch</a></li>
<li><a href="indonesian.md">Indonesian</a></li>
<li><a href="ukrainian.md">Ukrainian</a></li>
</ul>

285
markdown/readme/languages/ukrainian.md

@ -0,0 +1,285 @@
<h1 align="center" style="border-bottom: none">
<div>
<a href="https://www.nocodb.com">
<img src="/packages/nc-gui/assets/img/icons/512x512.png" width="80" />
<br>
NocoDB
</a>
</div>
Опенсорс альтернатива Airtable <br>
</h1>
<p align="center">
Nocodb перетворює будь-яку базу даних MySQL, PostgreSQL, SQL Server, SQLite та MariaDB в розумну електронну таблицю.
</p>
<div align="center">
[![Node version](https://img.shields.io/badge/node-%3E%3D%2018.14.0-brightgreen)](http://nodejs.org/download/)
[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-green.svg)](https://conventionalcommits.org)
</div>
<p align="center">
<a href="http://www.nocodb.com"><b>Сайт</b></a>
<a href="https://discord.gg/5RgZmkW"><b>Discord</b></a>
<a href="https://community.nocodb.com/"><b>Спільнота</b></a>
<a href="https://twitter.com/nocodb"><b>Twitter</b></a>
<a href="https://www.reddit.com/r/NocoDB/"><b>Reddit</b></a>
<a href="https://docs.nocodb.com/"><b>Документація</b></a>
</p>
![video avi](https://github.com/nocodb/nocodb/assets/86527202/e2fad786-f211-4dcb-9bd3-aaece83a6783)
<div align="center">
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263434-75fe793d-42af-49e4-b964-d70920e41655.png">](markdown/readme/languages/chinese.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263474-787d71e7-3a87-42a8-92a8-be1d1f55413d.png">](markdown/readme/languages/french.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263531-fae58600-6616-4b43-95a0-5891019dd35d.png">](markdown/readme/languages/german.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263669-f567196a-d4e8-4143-a80a-93d3be32ba90.png">](markdown/readme/languages/portuguese.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263707-ba4e04a4-268a-4626-91b8-048e572fd9f6.png">](markdown/readme/languages/italian.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263770-38e3e79d-11d4-472e-ac27-ae0f17cf65c4.png">](markdown/readme/languages/japanese.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263822-28fce9de-915a-44dc-962d-7a61d340e91d.png">](markdown/readme/languages/korean.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263888-151d4ad1-7084-4943-97c9-56f28cd40b80.png">](markdown/readme/languages/russian.md)
[<img height="38" src="https://user-images.githubusercontent.com/61551451/135263589-3dbeda9a-0d2e-4bbd-b1fc-691404bb74fb.png">](markdown/readme/languages/spanish.md)
[Українська](markdown/readme/languages/ukrainian.md)
</div>
<p align="center"><a href="markdown/readme/languages/README.md"><b>Дивіться інші мови »</b></a></p>
<img src="https://static.scarf.sh/a.png?x-pxid=c12a77cc-855e-4602-8a0f-614b2d0da56a" />
# Приєднуйтеся до нашої команди
<p align=""><a href="http://careers.nocodb.com" target="_blank"><img src="https://user-images.githubusercontent.com/61551451/169663818-45643495-e95b-48e2-be13-01d6a77dc2fd.png" width="250"/></a></p>
# Приєднуйтеся до нашої спільноти
<a href="https://discord.gg/5RgZmkW" target="_blank">
<img src="https://discordapp.com/api/guilds/661905455894888490/widget.png?style=banner3" alt="">
</a>
<!-- <a href="https://community.nocodb.com/" target="_blank">
<img src="https://i2.wp.com/www.feverbee.com/wp-content/uploads/2018/07/logo-discourse.png" alt="">
</a>
-->
[![Stargazers repo roster for @nocodb/nocodb](http://reporoster.com/stars/nocodb/nocodb)](https://github.com/nocodb/nocodb/stargazers)
# Швидка спроба проекту
## Docker
```bash
# для SQLite
docker run -d --name nocodb \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
nocodb/nocodb:latest
# для MySQL
docker run -d --name nocodb-mysql \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="mysql2://host.docker.internal:3306?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
# для PostgreSQL
docker run -d --name nocodb-postgres \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="pg://host.docker.internal:5432?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
# для MSSQL
docker run -d --name nocodb-mssql \
-v "$(pwd)"/nocodb:/usr/app/data/ \
-p 8080:8080 \
-e NC_DB="mssql://host.docker.internal:1433?u=root&p=password&d=d1" \
-e NC_AUTH_JWT_SECRET="569a1821-0a93-45e8-87ab-eb857f20a010" \
nocodb/nocodb:latest
```
> Щоб зберегти дані в Docker, ви можете змонтувати том в /usr/app/data/ з версії 0.10.6. В іншому випадку ваші дані будуть втрачені після перестворення контейнера.
> Якщо ви плануєте вводити будь-які спеціальні символи, вам може знадобитися змінити набір символів та порівняння при створенні бази даних. Будь ласка, перегляньте приклади для [MySQL Docker](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043).
> Різні команди лише вказують базу даних, яку NocoDB буде використовувати для зберігання метаданих, але це не впливає на можливість підключення до іншого типу бази даних.
## Binaries
##### MacOS (x64)
```bash
curl http://get.nocodb.com/macos-x64 -o nocodb -L && chmod +x nocodb && ./nocodb
```
##### MacOS (arm64)
```bash
curl http://get.nocodb.com/macos-arm64 -o nocodb -L && chmod +x nocodb && ./nocodb
```
##### Linux (x64)
```bash
curl http://get.nocodb.com/linux-x64 -o nocodb -L && chmod +x nocodb && ./nocodb
```
##### Linux (arm64)
```bash
curl http://get.nocodb.com/linux-arm64 -o nocodb -L && chmod +x nocodb && ./nocodb
```
##### Windows (x64)
```bash
iwr http://get.nocodb.com/win-x64.exe -o Noco-win-x64.exe
.\Noco-win-x64.exe
```
##### Windows (arm64)
```bash
iwr http://get.nocodb.com/win-arm64.exe -o Noco-win-arm64.exe
.\Noco-win-arm64.exe
```
## Docker Compose
Ми надаємо різні приклад конфігурацій docker-compose.yml у [цьому каталозі](https://github.com/nocodb/nocodb/tree/master/docker-compose). Ось деякі приклади.
```bash
git clone https://github.com/nocodb/nocodb
# для MySQL
cd nocodb/docker-compose/mysql
# для PostgreSQL
cd nocodb/docker-compose/pg
# для MSSQL
cd nocodb/docker-compose/mssql
docker-compose up -d
```
> Щоб зберегти дані в Docker, ви можете змонтувати том в /usr/app/data/ з версії 0.10.6. В іншому випадку ваші дані будуть втрачені після перестворення контейнера.
> Якщо ви плануєте вводити будь-які спеціальні символи, вам може знадобитися змінити набір символів та порівняння при створенні бази даних. Будь ласка, перегляньте приклади для [MySQL Docker](https://github.com/nocodb/nocodb/issues/1340#issuecomment-1049481043).
## NPX
Ви можете запустити нижченаведену команду, якщо вам потрібна інтерактивна конфігурація.
```
npx create-nocodb-app
```
<img src="https://user-images.githubusercontent.com/35857179/163672964-00ef5d62-0434-447d-ac01-3ebb780099b9.png" width="520px"/>
## Node Application
Для початку ви можете використати простий Node.js застосунок.
```bash
git clone https://github.com/nocodb/nocodb-seed
cd nocodb-seed
npm install
npm start
```
# GUI
Доступ до панелі інструментів за адресою: [http://localhost:8080/dashboard](http://localhost:8080/dashboard)
# Screenshots
![2](https://github.com/nocodb/nocodb/assets/86527202/a127c05e-2121-4af2-a342-128e0e2d0291)
![3](https://github.com/nocodb/nocodb/assets/86527202/674da952-8a06-4848-a0e8-a7b02d5f5c88)
![4](https://github.com/nocodb/nocodb/assets/86527202/cbc5152a-9caf-4f77-a8f7-92a9d06d025b)
![5](https://github.com/nocodb/nocodb/assets/86527202/dc75dfdc-c486-4f5a-a853-2a8f9e6b569a)
![5](https://user-images.githubusercontent.com/35857179/194844886-a17006e0-979d-493f-83c4-0e72f5a9b716.png)
![7](https://github.com/nocodb/nocodb/assets/86527202/be64e619-7295-43e2-aa95-cace4462b17f)
![8](https://github.com/nocodb/nocodb/assets/86527202/4538bf5a-371f-4ec1-a867-8197e5824286)
![8](https://user-images.githubusercontent.com/35857179/194844893-82d5e21b-ae61-41bd-9990-31ad659bf490.png)
![9](https://user-images.githubusercontent.com/35857179/194844897-cfd79946-e413-4c97-b16d-eb4d7678bb79.png)
![10](https://user-images.githubusercontent.com/35857179/194844902-c0122570-0dd5-41cf-a26f-6f8d71fefc99.png)
![11](https://user-images.githubusercontent.com/35857179/194844903-c1e47f40-e782-4f5d-8dce-6449cc70b181.png)
![12](https://user-images.githubusercontent.com/35857179/194844907-09277d3e-cbbf-465c-9165-6afc4161e279.png)
# Функції
### Багатий інтерфейс таблиць
- ⚡ &nbsp;Основні операції: Створення, Читання, Оновлення та Видалення Таблиць, Стовпців та Рядків
- ⚡ &nbsp;Операції з полями: Сортування, Фільтрація, Приховування / Розкриття Стовпців
- ⚡ &nbsp;Типи переглядів: Сітка (за замовчуванням), Галерея, Форма та Канбан
- ⚡ &nbsp;Типи дозволів для переглядів: Спільний доступ для переглядів та Заблоковані перегляди
- ⚡ &nbsp;Поділ баз / переглядів: Публічно або Приватно (з паролем)
- ⚡ &nbsp;Варіанти типів клітинок: ID, ПосиланняНаІншийЗапис, Пошук, Сума, ОдноРядковийТекст, Вкладення, Валюта, Формула, тощо
- ⚡ &nbsp;Контроль доступу за ролями: Деталізований контроль доступу на різних рівнях
- ⚡ &nbsp;і більше ...
### Широкий вибір застосунків для автоматизації робочих процесів
Ми надаємо різні інтеграції у трьох основних категоріях. Деталі дивіться у <a href="https://docs.nocodb.com/setup-and-usages/account-settings#app-store" target="_blank">App Store</a>.
- ⚡ &nbsp;Чат: Slack, Discord, Mattermost, тощо
- ⚡ &nbsp;Електронна пошта: AWS SES, SMTP, MailerSend, тощо
- ⚡ &nbsp;Сховище: AWS S3, Google Cloud Storage, Minio, тощо
### API доступ
Ми надаємо різні способи, якими користувачі можуть програмно викликати дії. Ви можете використовувати токен (або JWT, або соціальний авторизаційний токен) для підписання ваших запитань для авторизації в NocoDB.
- ⚡ &nbsp;REST API
- ⚡ &nbsp;NocoDB SDK
### Синхронізація схеми
Ми дозволяємо вам синхронізувати зміни схеми, якщо ви внесли зміни поза NocoDB GUI. Проте слід зауважити, що вам доведеться мати власні міграції схеми для переміщення з одного середовища в інше. Деталі дивіться у <a href="https://docs.nocodb.com/setup-and-usages/sync-schema/" target="_blank">Sync Schema</a>.
### Аудит
Ми зберігаємо всі журнали операцій користувача в одному місці. Деталі дивіться у <a href="https://docs.nocodb.com/setup-and-usages/audit" target="_blank">Audit</a>.
# Налаштування продукції
За замовчуванням використовується SQLite для зберігання метаданих. Однак ви можете вказати свою базу даних. Параметри підключення до цієї бази даних можна вказати в змінній середовища `NC_DB`. Крім того, ми також надаємо наступні змінні середовища для налаштувань.
## Змінні середовища
Будь ласка, звертайтеся до [Змінні середовища](https://docs.nocodb.com/getting-started/environment-variables)
# Налаштування розробки
Будь ласка, перегляньте всю необхідну інформацію тут [Development Setup](https://docs.nocodb.com/engineering/development-setup)
# Вклад у проект
Будь ласка, перегляньте всю необхідну інформацію тут [Contribution Guide](https://github.com/nocodb/nocodb/blob/master/.github/CONTRIBUTING.md).
# Чому ми створюємо цей проект?
Більшість інтернет-бізнесів використовують електронні таблиці, або бази даних для вирішення своїх бізнес-потреб. Електронні таблиці використовуються мільярдами людей кожен день. Однак ми далекі від роботи на подібних швидкостях у роботі з базами даних, які є набагато потужнішими інструментами, коли мова йде про обчислення. Спроби вирішити це за допомогою пропозицій SaaS приводить до жахливого контролю доступу, в'язницю від постачальників хмарних сервісів, в'язницю даних, раптові зміни цін та, що найважливіше, скляну стелю щодо того, що можливо у майбутньому.
# Наша місія
Наша місія - надати найпотужніший no-code інтерфейс для баз даних, код яких є відкритим для кожного інтернет-бізнесу в світі. Це не лише демократизує доступ до потужного інструмента для обчислень, але також приведе до того, що мільярди людей матимуть неймовірні можливості до експериментів та створення проектів в інтернеті.
# Ліцензія
<p>
Цей проект ліцензується за <a href="./LICENSE">AGPLv3</a>.
</p>
# Співтовариші
Дякуємо за ваші внески! Ми вдячні за всі внески від спільноти.
<a href="https://github.com/nocodb/nocodb/graphs/contributors">
<img src="https://contrib.rocks/image?repo=nocodb/nocodb" />
</a>

17
packages/nc-gui/components/shared-view/Gallery.vue

@ -1,5 +1,18 @@
<script setup lang="ts">
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '#imports'
import {
ActiveViewInj,
FieldsInj,
IsPublicInj,
MetaInj,
ReadonlyInj,
ReloadViewDataHookInj,
createEventHook,
provide,
useProvideKanbanViewStore,
useProvideSmartsheetStore,
useProvideViewColumns,
useSharedView,
} from '#imports'
const { sharedView, meta, nestedFilters } = useSharedView()
@ -20,6 +33,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideKanbanViewStore(meta, sharedView)
</script>
<template>

3
packages/nc-gui/components/smartsheet/Kanban.vue

@ -291,7 +291,8 @@ const kanbanListScrollHandler = useDebounceFn(async (e: any) => {
const stackTitle = e.target.getAttribute('data-stack-title')
const pageSize = appInfo.value.defaultLimit || 25
const stack = formattedData.value.get(stackTitle)
if (stack) {
if (stack && (countByStack.value.get(stackTitle) === undefined || stack.length < countByStack.value.get(stackTitle)!)) {
const page = Math.ceil(stack.length / pageSize)
await loadMoreKanbanData(stackTitle, { offset: page * pageSize })
}

45
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -168,7 +168,9 @@ const isExpanded = useVModel(props, 'modelValue', emits, {
})
const onClose = () => {
if (changedColumns.value.size > 0) {
if (!isUIAllowed('dataEdit')) {
isExpanded.value = false
} else if (changedColumns.value.size > 0) {
isCloseModalOpen.value = true
} else {
if (_row.value?.rowMeta?.new) emits('cancel')
@ -457,7 +459,7 @@ const onIsExpandedUpdate = (v: boolean) => {
if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) {
isExpanded.value = v
} else if (!v) {
} else if (!v && isUIAllowed('dataEdit')) {
preventModalStatus.value = true
} else {
isExpanded.value = v
@ -555,10 +557,11 @@ export default {
</div>
<div class="flex gap-2">
<NcButton
v-if="!isNew"
v-if="!isNew && rowId"
type="secondary"
class="!xs:hidden text-gray-700"
@click="!isNew ? copyRecordUrl() : () => {}"
:disabled="isLoading"
@click="copyRecordUrl()"
>
<div v-e="['c:row-expand:copy-url']" data-testid="nc-expanded-form-copy-url" class="flex gap-2 items-center">
<component :is="iconMap.check" v-if="isRecordLinkCopied" class="cursor-pointer nc-duplicate-row" />
@ -567,28 +570,18 @@ export default {
</div>
</NcButton>
<NcDropdown v-if="!isNew" placement="bottomRight">
<NcButton type="secondary" class="nc-expand-form-more-actions w-10">
<GeneralIcon icon="threeDotVertical" class="text-md text-gray-700" />
<NcButton type="secondary" class="nc-expand-form-more-actions w-10" :disabled="isLoading">
<GeneralIcon icon="threeDotVertical" class="text-md" :class="isLoading ? 'text-gray-300' : 'text-gray-700'" />
</NcButton>
<template #overlay>
<NcMenu>
<NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()">
<NcMenuItem class="text-gray-700" @click="_loadRow()">
<div v-e="['c:row-expand:reload']" class="flex gap-2 items-center" data-testid="nc-expanded-form-reload">
<component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }}
</div>
</NcMenuItem>
<NcMenuItem v-if="!isNew && isMobileMode" class="text-gray-700" @click="!isNew ? copyRecordUrl() : () => {}">
<div v-e="['c:row-expand:copy-url']" data-testid="nc-expanded-form-copy-url" class="flex gap-2 items-center">
<component :is="iconMap.link" class="cursor-pointer nc-duplicate-row" />
{{ $t('labels.copyRecordURL') }}
</div>
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew"
class="text-gray-700"
@click="!isNew ? onDuplicateRow() : () => {}"
>
<NcMenuItem v-if="isUIAllowed('dataEdit')" class="text-gray-700" @click="!isNew ? onDuplicateRow() : () => {}">
<div
v-e="['c:row-expand:duplicate']"
data-testid="nc-expanded-form-duplicate"
@ -600,9 +593,9 @@ export default {
</span>
</div>
</NcMenuItem>
<NcDivider v-if="isUIAllowed('dataEdit') && !isNew" />
<NcDivider v-if="isUIAllowed('dataEdit')" />
<NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew"
v-if="isUIAllowed('dataEdit')"
class="!text-red-500 !hover:bg-red-50"
@click="!isNew && onDeleteRowClick()"
>
@ -808,17 +801,23 @@ export default {
class="w-full h-16 border-t-1 border-gray-200 bg-white flex items-center justify-end p-3 xs:(p-0 mt-4 border-t-0 gap-x-4 justify-between)"
>
<NcDropdown v-if="!isNew && isMobileMode" placement="bottomRight">
<NcButton type="secondary" class="nc-expand-form-more-actions w-10">
<GeneralIcon icon="threeDotVertical" class="text-md text-gray-700" />
<NcButton type="secondary" class="nc-expand-form-more-actions w-10" :disabled="isLoading">
<GeneralIcon icon="threeDotVertical" class="text-md" :class="isLoading ? 'text-gray-300' : 'text-gray-700'" />
</NcButton>
<template #overlay>
<NcMenu>
<NcMenuItem v-if="!isNew" class="text-gray-700" @click="_loadRow()">
<NcMenuItem class="text-gray-700" @click="_loadRow()">
<div v-e="['c:row-expand:reload']" class="flex gap-2 items-center" data-testid="nc-expanded-form-reload">
<component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }}
</div>
</NcMenuItem>
<NcMenuItem v-if="rowId" class="text-gray-700" @click="!isNew ? copyRecordUrl() : () => {}">
<div v-e="['c:row-expand:copy-url']" data-testid="nc-expanded-form-copy-url" class="flex gap-2 items-center">
<component :is="iconMap.link" class="cursor-pointer nc-duplicate-row" />
{{ $t('labels.copyRecordURL') }}
</div>
</NcMenuItem>
<NcDivider />
<NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew"

11
packages/nc-gui/composables/useExpandedFormStore.ts

@ -44,14 +44,19 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const changedColumns = ref(new Set<string>())
const { base } = storeToRefs(useBase())
const row = ref<Row>(_row.value.rowMeta.new ? _row.value : ({ row: {}, oldRow: {}, rowMeta: {} } as Row))
const { sharedView } = useSharedView()
const row = ref<Row>(
sharedView.value?.type === ViewTypes.GALLERY || sharedView.value?.type === ViewTypes.KANBAN || _row.value.rowMeta.new
? _row.value
: ({ row: {}, oldRow: {}, rowMeta: {} } as Row),
)
const rowStore = useProvideSmartsheetRowStore(meta, row)
const activeView = inject(ActiveViewInj, ref())
const { sharedView } = useSharedView()
const { addUndo, clone, defineViewScope } = useUndoRedo()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())

46
packages/nc-gui/lang/pl.json

@ -157,8 +157,8 @@
"groupingField": "Pole grupowania",
"insertAfter": "Wstaw po",
"insertBefore": "Wstaw przed",
"insertAbove": "Insert above",
"insertBelow": "Insert below",
"insertAbove": "Wstaw powyżej",
"insertBelow": "Wstaw poniżej",
"hideField": "Ukryj pole",
"sortAsc": "Sortuj rosnąco",
"sortDesc": "Sortuj malejąco",
@ -191,8 +191,8 @@
"shift": "Zmiana",
"enter": "Wprowadź",
"seconds": "Sekundy",
"paste": "Paste",
"restore": "Restore"
"paste": "Wklej",
"restore": "Przywróć"
},
"objects": {
"workspace": "Obszar roboczy",
@ -647,13 +647,13 @@
"newFormLoaded": "Nowy formularz zostanie załadowany po",
"webhook": "Webhook",
"multiField": {
"newField": "New field",
"saveChanges": "Save changes",
"updatedField": "Updated field",
"deletedField": "Deleted field",
"incompleteConfiguration": "Incomplete configuration",
"selectField": "Select a field",
"selectFieldLabel": "Make changes to field properties by selecting a field from the list"
"newField": "Nowe pole",
"saveChanges": "Zapisz zmiany",
"updatedField": "Zaktualizowane pole",
"deletedField": "Usunięte pole",
"incompleteConfiguration": "Niekompletna konfiguracja",
"selectField": "Wybierz pole",
"selectFieldLabel": "Dokonaj zmian we właściwościach pola wybierając pole z listy"
}
},
"activity": {
@ -680,7 +680,7 @@
"enablePublicAccess": "Włącz dostęp publiczny",
"doYouWantToSaveTheChanges": "Czy chcesz zapisać zmiany?",
"editingAccess": "Edycja dostępu",
"enabledPublicViewing": "Enable Public Viewing",
"enabledPublicViewing": "Włącz widok publiczny",
"restrictAccessWithPassword": "Ogranicz dostęp hasłem",
"manageProjectAccess": "Zarządzaj dostępem do bazy",
"allowDownload": "Zezwól na pobieranie",
@ -733,7 +733,7 @@
"groupBy": "Grupuj według",
"addSubGroup": "Dodaj podgrupę",
"shareBase": {
"label": "Share Base",
"label": "Udostępnij bazę",
"disable": "Wyłącz współdzielenie",
"enable": "Włącz każdy z linkiem",
"link": "Link udostępniający bazę"
@ -1034,7 +1034,7 @@
"acceptOnlyValid": "Akceptuje tylko",
"apiTokenCreate": "Utwórz osobiste tokeny API do użytku w automatyzacji lub zewnętrznych aplikacjach.",
"selectFieldToSort": "Wybierz pole do sortowania",
"selectFieldToGroup": "Select Field to Group",
"selectFieldToGroup": "Wybierz pole do grupowania",
"thereAreNoRecordsInTable": "Nie ma rekordów w tabeli",
"createWebhookMsg1": "Rozpocznij pracę z webhookami!",
"createWebhookMsg2": "Utwórz webhoooki do zasilania twoich automatyzacji,",
@ -1062,9 +1062,9 @@
"duplicateProject": "Czy na pewno chcesz zduplikować bazę?",
"duplicateTable": "Czy na pewno chcesz zduplikować tabelę?",
"multiField": {
"fieldVisibility": "You cannot change visibility of a field that is being edited. Please save or discard changes first.",
"moveEditedField": "You cannot move field that is being edited. Either save or discard changes first",
"moveDeletedField": "You cannot move field that is deleted. Either save or discard changes first"
"fieldVisibility": "Nie można zmienić widoczności pola, które jest edytowane. Proszę najpierw zapisać lub odrzucić zmiany.",
"moveEditedField": "Nie można przenieść edytowanego pola. Proszę najpierw zapisać lub odrzucić zmiany",
"moveDeletedField": "Nie można przenieść pola, które zostało usunięte. Proszę najpierw zapisać lub odrzucić zmiany"
}
},
"info": {
@ -1228,8 +1228,8 @@
"goToNext": "Przejdź do następnego",
"thankYou": "Dziękuję!",
"submittedFormData": "Pomyślnie przesłałeś dane formularza.",
"editingSystemKeyNotSupported": "Editing system key not supported",
"notAvailableAtTheMoment": "Not available at the moment"
"editingSystemKeyNotSupported": "Edycja klucza systemowego nie jest obsługiwana",
"notAvailableAtTheMoment": "Obecnie niedostępne"
},
"error": {
"nameRequired": "Wymagana nazwa",
@ -1292,7 +1292,7 @@
"followingCharactersAreNotAllowed": "Następujące znaki są niedozwolone",
"columnNameRequired": "Nazwa kolumny jest wymagana",
"duplicateColumnName": "Zduplikowana nazwa kolumny",
"duplicateSystemColumnName": "Name already used for system field",
"duplicateSystemColumnName": "Nazwa jest już używana dla pola systemowego",
"uiDataTypeRequired": "Typ danych UI jest wymagany",
"columnNameExceedsCharacters": "Nazwa kolumny przekracza maksymalną długość {value} znaków",
"projectNameExceeds50Characters": "Nazwa projektu przekracza 50 znaków",
@ -1307,9 +1307,9 @@
"fieldRequired": "{value} nie może być puste.",
"projectNotAccessible": "Projekt niedostępny",
"copyToClipboardError": "Nie udało się skopiować do schowka",
"pasteFromClipboardError": "Failed to paste from clipboard",
"multiFieldSaveValidation": "Please complete the configuration of all fields before saving",
"somethingWentWrong": "Something went wrong"
"pasteFromClipboardError": "Nie udało się wkleić ze schowka",
"multiFieldSaveValidation": "Proszę zakończyć konfigurację wszystkich pól przed zapisaniem",
"somethingWentWrong": "Coś poszło nie tak"
},
"toast": {
"exportMetadata": "Pomyślnie wyeksportowano metadane projektu",

4
packages/noco-docs/docs/020.getting-started/050.self-hosted/010.installation.md

@ -179,7 +179,7 @@ curl http://get.nocodb.com/linux-arm64 -o nocodb -L \
<TabItem value="Windows (x64)" label="Windows (x64)">
```bash
iwr http://get.nocodb.com/win-x64.exe
iwr http://get.nocodb.com/win-x64.exe -OutFile "Noco-win-x64.exe"
.\Noco-win-x64.exe
```
@ -187,7 +187,7 @@ iwr http://get.nocodb.com/win-x64.exe
<TabItem value="Windows (arm64)" label="Windows (arm64)">
```bash
iwr http://get.nocodb.com/win-arm64.exe
iwr http://get.nocodb.com/win-arm64.exe -OutFile "Noco-win-arm64.exe"
.\Noco-win-arm64.exe
```

1
packages/nocodb/src/db/BaseModelSqlv2.ts

@ -6216,6 +6216,7 @@ function shouldSkipField(
if (fieldsSet) {
return !fieldsSet.has(column.title);
} else {
if (column.system && isCreatedOrLastModifiedByCol(column)) return true;
if (!extractPkAndPv) {
if (!(viewOrTableColumn instanceof Column)) {
if (

7
packages/nocodb/src/models/Column.ts

@ -153,6 +153,13 @@ export default class Column<T = any> implements ColumnType {
if (insertObj.meta && typeof insertObj.meta === 'object') {
insertObj.meta = JSON.stringify(insertObj.meta);
}
insertObj.order =
column.order ??
(await ncMeta.metaGetNextOrder(MetaTable.COLUMNS, {
fk_model_id: column.fk_model_id,
}));
if (column.validate) {
if (typeof column.validate === 'string')
insertObj.validate = column.validate;

19
packages/nocodb/tests/unit/model/tests/baseModelSql.test.ts

@ -54,13 +54,6 @@ function baseModelSqlTests() {
);
const insertedRow = (await baseModelSql.list())[0];
inputData.CreatedBy = {
id: context.user.id,
email: context.user.email,
display_name: context.user.display_name,
};
inputData.UpdatedBy = null;
expect(insertedRow).to.deep.include(inputData);
expect(insertedRow).to.deep.include(response);
@ -176,18 +169,10 @@ function baseModelSqlTests() {
const insertedRows: any[] = await baseModelSql.list();
await baseModelSql.bulkUpdate(
insertedRows.map(
({
CreatedAt: _,
UpdatedAt: __,
CreatedBy: ___,
UpdatedBy: ____,
...row
}) => ({
insertedRows.map(({ CreatedAt: _, UpdatedAt: __, ...row }) => ({
...row,
Title: `new-${row['Title']}`,
}),
),
})),
{ cookie: request },
);

59
packages/nocodb/tests/unit/rest/tests/columnTypeSpecific.test.ts

@ -52,6 +52,16 @@ function columnTypeSpecificTests() {
uidt: UITypes.LastModifiedTime,
system: true,
},
{
title: 'nc_created_by',
uidt: UITypes.CreatedBy,
system: true,
},
{
title: 'nc_updated_by',
uidt: UITypes.LastModifiedBy,
system: true,
},
];
describe('Qr Code Column', () => {
@ -176,7 +186,9 @@ function columnTypeSpecificTests() {
for (let i = 0; i < defaultTableColumns.length; i++) {
expect(columns[i].title).to.equal(defaultTableColumns[i].title);
expect(columns[i].uidt).to.equal(defaultTableColumns[i].uidt);
expect(columns[i].system).to.equal(defaultTableColumns[i].system);
expect(Boolean(columns[i].system)).to.equal(
defaultTableColumns[i].system,
);
}
});
@ -214,7 +226,7 @@ function columnTypeSpecificTests() {
expect(unfilteredRecords[0].UpdatedAt).to.equal(null);
});
it('Modify record: verify last-modified-at & modified-by is updated', async () => {
it('Modify record: verify last-modified-at is updated', async () => {
// get current date time
const currentDateTime = new Date();
const d1 = new Date();
@ -280,13 +292,6 @@ function columnTypeSpecificTests() {
// calculate difference between current date time and stored date time
difference = storedDateTime2.getTime() - storedDateTime1.getTime();
expect(difference).to.be.greaterThan(1500);
// verify modified by
expect(updatedRecord[0].UpdatedBy).to.not.equal(null);
expect(updatedRecord[0].UpdatedBy[0].email).to.equal(
'test@example.com',
);
expect(updatedRecord[0].UpdatedBy[0].display_name).to.equal(null);
});
it('Modify record: verify that system fields are RO', async () => {
@ -313,8 +318,8 @@ function columnTypeSpecificTests() {
.send([
{
Id: unfilteredRecords[0].Id,
CreatedBy: 'test@example.com',
UpdatedBy: 'test@example.com',
nc_created_by: 'test@example.com',
nc_updated_by: 'test@example.com',
},
])
.expect(400);
@ -341,9 +346,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedTime,
);
expect(columns.columns[defaultTableColumns.length].system).to.equal(
false,
);
expect(
Boolean(columns.columns[defaultTableColumns.length].system),
).to.equal(false);
expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2);
@ -391,9 +396,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedBy,
);
expect(columns.columns[defaultTableColumns.length].system).to.equal(
false,
);
expect(
Boolean(columns.columns[defaultTableColumns.length].system),
).to.equal(false);
expect(records[0].CreatedBy).to.deep.equal({
id: context.user.id,
email: context.user.email,
@ -406,10 +411,10 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length + 1].uidt).to.equal(
UITypes.LastModifiedBy,
);
expect(columns.columns[defaultTableColumns.length + 1].system).to.equal(
false,
);
expect(records[0].UpdatedBy).to.deep.equal(null);
expect(
Boolean(columns.columns[defaultTableColumns.length + 1].system),
).to.equal(false);
expect(records[0].LastModifiedBy).to.deep.equal(null);
// update record should fail
await request(context.app)
@ -468,9 +473,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedTime,
);
expect(columns.columns[defaultTableColumns.length].system).to.equal(
false,
);
expect(
Boolean(columns.columns[defaultTableColumns.length].system),
).to.equal(false);
expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2);
});
@ -505,9 +510,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedBy,
);
expect(columns.columns[defaultTableColumns.length].system).to.equal(
false,
);
expect(
Boolean(columns.columns[defaultTableColumns.length].system),
).to.equal(false);
expect(records[0].CreatedBy).to.deep.equal(records[0].CreatedBy);
});
});

37
packages/nocodb/tests/unit/rest/tests/newDataApis.test.ts

@ -140,6 +140,7 @@ const unauthorizedResponse = process.env.EE !== 'true' ? 404 : 403;
const verifyColumnsInRsp = (row, columns: ColumnType[]) => {
const responseColumnsListStr = Object.keys(row).sort().join(',');
const expectedColumnsListStr = columns
.filter((c) => !(c.system && isCreatedOrLastModifiedByCol(c)))
.map((c) => c.title)
.sort()
.join(',');
@ -680,12 +681,7 @@ function textBased() {
expect(
verifyColumnsInRsp(
rsp.body.list[0],
columns.filter(
(c) =>
(!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c)) ||
!c.system,
),
columns.filter((c) => !isCreatedOrLastModifiedTimeCol(c) || !c.system),
),
).to.equal(true);
const filteredArray = rsp.body.list.map((r) => r.SingleLineText);
@ -706,9 +702,7 @@ function textBased() {
const displayColumns = columns.filter(
(c) =>
c.title !== 'SingleLineText' &&
((!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c)) ||
!c.system),
(!isCreatedOrLastModifiedTimeCol(c) || !c.system),
);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
});
@ -757,8 +751,7 @@ function textBased() {
(c) =>
c.title !== 'MultiLineText' &&
c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c),
!isCreatedOrLastModifiedTimeCol(c),
);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
return gridView;
@ -778,8 +771,7 @@ function textBased() {
(c) =>
c.title !== 'MultiLineText' &&
c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c),
!isCreatedOrLastModifiedTimeCol(c),
);
expect(rsp.body.pageInfo.totalRows).to.equal(61);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
@ -801,8 +793,7 @@ function textBased() {
(c) =>
c.title !== 'MultiLineText' &&
c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c),
!isCreatedOrLastModifiedTimeCol(c),
);
expect(rsp.body.pageInfo.totalRows).to.equal(7);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
@ -894,9 +885,11 @@ function textBased() {
query: {
offset: 10000,
},
status: 200,
status: 400,
});
expect(rsp.body.list.length).to.equal(0);
expect(rsp.body.msg).to.equal(
'Offset is beyond the total number of records',
);
});
it('List: invalid sort, filter, fields', async function () {
@ -2155,12 +2148,14 @@ function linkBased() {
expect(rsp.body.list.length).to.equal(25);
rsp.body.list.sort((a, b) => a.Id - b.Id);
// paginated response, limit to 25
/* TODO enable this after fix
for (let i = 1; i <= 25; i++) {
expect(rsp.body.list[i - 1]).to.deep.equal({
Id: i,
Film: `Film ${i}`,
});
}
*/
// verify in Film table
for (let i = 21; i <= 30; i++) {
@ -2516,7 +2511,7 @@ function linkBased() {
}
}
async function nestedListTests(validParams) {
async function nestedListTests(validParams, relationType?) {
// Link List: Invalid table ID
if (debugMode) console.log('Link List: Invalid table ID');
await ncAxiosLinkGet({
@ -2572,7 +2567,8 @@ function linkBased() {
await ncAxiosLinkGet({
...validParams,
query: { ...validParams.query, offset: 9999 },
status: 200,
// for BT relation we use btRead so we don't apply offset & limit, also we don't return page info where this check is done
status: relationType === 'bt' ? 200 : 400,
});
// Link List: Invalid query parameter - negative limit
@ -2736,7 +2732,7 @@ function linkBased() {
status: 200,
};
await nestedListTests(validParams);
await nestedListTests(validParams, 'bt');
});
// Error handling (many-many)
@ -3062,7 +3058,6 @@ export default function () {
describe('Date based', dateBased);
describe('Link based', linkBased);
describe('User field based', userFieldBased);
// based out of Sakila db, for link based tests
describe('General', generalDb);
}

2
tests/playwright/pages/SharedForm/index.ts

@ -20,6 +20,8 @@ export class SharedFormPage extends BasePage {
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/rows',
});
await this.rootPage.waitForTimeout(200);
}
async verifySuccessMessage() {

Loading…
Cancel
Save