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. 104
      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="russian.md">Russian</a></li>
<li><a href="dutch.md">Dutch</a></li> <li><a href="dutch.md">Dutch</a></li>
<li><a href="indonesian.md">Indonesian</a></li> <li><a href="indonesian.md">Indonesian</a></li>
<li><a href="ukrainian.md">Ukrainian</a></li>
</ul> </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"> <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() const { sharedView, meta, nestedFilters } = useSharedView()
@ -20,6 +33,8 @@ provide(IsPublicInj, ref(true))
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
useProvideKanbanViewStore(meta, sharedView)
</script> </script>
<template> <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 stackTitle = e.target.getAttribute('data-stack-title')
const pageSize = appInfo.value.defaultLimit || 25 const pageSize = appInfo.value.defaultLimit || 25
const stack = formattedData.value.get(stackTitle) 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) const page = Math.ceil(stack.length / pageSize)
await loadMoreKanbanData(stackTitle, { offset: page * 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 = () => { const onClose = () => {
if (changedColumns.value.size > 0) { if (!isUIAllowed('dataEdit')) {
isExpanded.value = false
} else if (changedColumns.value.size > 0) {
isCloseModalOpen.value = true isCloseModalOpen.value = true
} else { } else {
if (_row.value?.rowMeta?.new) emits('cancel') if (_row.value?.rowMeta?.new) emits('cancel')
@ -457,7 +459,7 @@ const onIsExpandedUpdate = (v: boolean) => {
if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) { if (changedColumns.value.size === 0 && !isUnsavedFormExist.value) {
isExpanded.value = v isExpanded.value = v
} else if (!v) { } else if (!v && isUIAllowed('dataEdit')) {
preventModalStatus.value = true preventModalStatus.value = true
} else { } else {
isExpanded.value = v isExpanded.value = v
@ -555,10 +557,11 @@ export default {
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<NcButton <NcButton
v-if="!isNew" v-if="!isNew && rowId"
type="secondary" type="secondary"
class="!xs:hidden text-gray-700" 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"> <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" /> <component :is="iconMap.check" v-if="isRecordLinkCopied" class="cursor-pointer nc-duplicate-row" />
@ -567,28 +570,18 @@ export default {
</div> </div>
</NcButton> </NcButton>
<NcDropdown v-if="!isNew" placement="bottomRight"> <NcDropdown v-if="!isNew" placement="bottomRight">
<NcButton type="secondary" class="nc-expand-form-more-actions w-10"> <NcButton type="secondary" class="nc-expand-form-more-actions w-10" :disabled="isLoading">
<GeneralIcon icon="threeDotVertical" class="text-md text-gray-700" /> <GeneralIcon icon="threeDotVertical" class="text-md" :class="isLoading ? 'text-gray-300' : 'text-gray-700'" />
</NcButton> </NcButton>
<template #overlay> <template #overlay>
<NcMenu> <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"> <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" /> <component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem v-if="!isNew && isMobileMode" class="text-gray-700" @click="!isNew ? copyRecordUrl() : () => {}"> <NcMenuItem v-if="isUIAllowed('dataEdit')" class="text-gray-700" @click="!isNew ? onDuplicateRow() : () => {}">
<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() : () => {}"
>
<div <div
v-e="['c:row-expand:duplicate']" v-e="['c:row-expand:duplicate']"
data-testid="nc-expanded-form-duplicate" data-testid="nc-expanded-form-duplicate"
@ -600,9 +593,9 @@ export default {
</span> </span>
</div> </div>
</NcMenuItem> </NcMenuItem>
<NcDivider v-if="isUIAllowed('dataEdit') && !isNew" /> <NcDivider v-if="isUIAllowed('dataEdit')" />
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew" v-if="isUIAllowed('dataEdit')"
class="!text-red-500 !hover:bg-red-50" class="!text-red-500 !hover:bg-red-50"
@click="!isNew && onDeleteRowClick()" @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)" 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"> <NcDropdown v-if="!isNew && isMobileMode" placement="bottomRight">
<NcButton type="secondary" class="nc-expand-form-more-actions w-10"> <NcButton type="secondary" class="nc-expand-form-more-actions w-10" :disabled="isLoading">
<GeneralIcon icon="threeDotVertical" class="text-md text-gray-700" /> <GeneralIcon icon="threeDotVertical" class="text-md" :class="isLoading ? 'text-gray-300' : 'text-gray-700'" />
</NcButton> </NcButton>
<template #overlay> <template #overlay>
<NcMenu> <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"> <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" /> <component :is="iconMap.reload" class="cursor-pointer" />
{{ $t('general.reload') }} {{ $t('general.reload') }}
</div> </div>
</NcMenuItem> </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 /> <NcDivider />
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew" 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 changedColumns = ref(new Set<string>())
const { base } = storeToRefs(useBase()) 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 rowStore = useProvideSmartsheetRowStore(meta, row)
const activeView = inject(ActiveViewInj, ref()) const activeView = inject(ActiveViewInj, ref())
const { sharedView } = useSharedView()
const { addUndo, clone, defineViewScope } = useUndoRedo() const { addUndo, clone, defineViewScope } = useUndoRedo()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())

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

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

104
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)"> <TabItem value="Windows (x64)" label="Windows (x64)">
```bash ```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 .\Noco-win-x64.exe
``` ```
@ -187,7 +187,7 @@ iwr http://get.nocodb.com/win-x64.exe
<TabItem value="Windows (arm64)" label="Windows (arm64)"> <TabItem value="Windows (arm64)" label="Windows (arm64)">
```bash ```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 .\Noco-win-arm64.exe
``` ```
@ -212,23 +212,23 @@ npm start
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Create ECS Cluster #### Create ECS Cluster
``` ```
aws ecs create-cluster \ aws ecs create-cluster \
--cluster-name <YOUR_ECS_CLUSTER> --cluster-name <YOUR_ECS_CLUSTER>
``` ```
#### Create Log group #### Create Log group
``` ```
aws logs create-log-group \ aws logs create-log-group \
--log-group-name /ecs/<YOUR_APP_NAME>/<YOUR_CONTAINER_NAME> --log-group-name /ecs/<YOUR_APP_NAME>/<YOUR_CONTAINER_NAME>
``` ```
#### Create ECS Task Definiton #### Create ECS Task Definiton
Every time you create it, it will add a new version. If it is not existing, the version will be 1. Every time you create it, it will add a new version. If it is not existing, the version will be 1.
```bash ```bash
aws ecs register-task-definition \ aws ecs register-task-definition \
@ -239,7 +239,7 @@ npm start
This json file defines the container specification. You can define secrets such as NC_DB and environment variables here. This json file defines the container specification. You can define secrets such as NC_DB and environment variables here.
::: :::
Here's the sample Task Definition Here's the sample Task Definition
```json ```json
{ {
@ -281,7 +281,7 @@ This json file defines the container specification. You can define secrets such
} }
``` ```
#### Create ECS Service #### Create ECS Service
```bash ```bash
aws ecs create-service \ aws ecs create-service \
@ -309,9 +309,9 @@ If your service fails to start, you may check the logs in ECS console or in Clou
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Pull NocoDB Image on Cloud Shell #### Pull NocoDB Image on Cloud Shell
Since Cloud Run only supports images from Google Container Registry (GCR) or Artifact Registry, we need to pull NocoDB image, tag it and push it in GCP using Cloud Shell. Here are some sample commands which you can execute in Cloud Shell. Since Cloud Run only supports images from Google Container Registry (GCR) or Artifact Registry, we need to pull NocoDB image, tag it and push it in GCP using Cloud Shell. Here are some sample commands which you can execute in Cloud Shell.
```bash ```bash
# pull latest NocoDB image # pull latest NocoDB image
@ -324,7 +324,7 @@ If your service fails to start, you may check the logs in ECS console or in Clou
docker push gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest docker push gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest
``` ```
#### Deploy NocoDB on Cloud Run #### Deploy NocoDB on Cloud Run
```bash ```bash
gcloud run deploy --image=gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest \ gcloud run deploy --image=gcr.io/<MY_PROJECT_ID>/nocodb/nocodb:latest \
@ -340,40 +340,40 @@ If your service fails to start, you may check the logs in ECS console or in Clou
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Create Apps #### Create Apps
On Home page, Click on Create icon & Select Apps (Deploy your code). On Home page, Click on Create icon & Select Apps (Deploy your code).
![Screenshot 2022-02-19 at 12 17 43 PM](https://user-images.githubusercontent.com/86527202/154790558-f8fe5580-5a58-412c-9c2e-145587712bf2.png) ![Screenshot 2022-02-19 at 12 17 43 PM](https://user-images.githubusercontent.com/86527202/154790558-f8fe5580-5a58-412c-9c2e-145587712bf2.png)
#### Choose Source: Docker Hub #### Choose Source: Docker Hub
![Screenshot 2022-02-19 at 12 22 01 PM](https://user-images.githubusercontent.com/86527202/154790563-b5b6d5b4-0bdc-4718-8cea-0a7ee52f283b.png) ![Screenshot 2022-02-19 at 12 22 01 PM](https://user-images.githubusercontent.com/86527202/154790563-b5b6d5b4-0bdc-4718-8cea-0a7ee52f283b.png)
#### Choose Source: Repository #### Choose Source: Repository
Configure Source Repository as `nocodb/nocodb`. Optionally you can pick release tag if you are interested in specific NocoDB version. Configure Source Repository as `nocodb/nocodb`. Optionally you can pick release tag if you are interested in specific NocoDB version.
![Screenshot 2022-02-19 at 12 23 11 PM](https://user-images.githubusercontent.com/86527202/154790564-1dcb5e33-3a57-471a-a44c-835a410a0cb7.png) ![Screenshot 2022-02-19 at 12 23 11 PM](https://user-images.githubusercontent.com/86527202/154790564-1dcb5e33-3a57-471a-a44c-835a410a0cb7.png)
#### [Optional] Additional Configurations #### [Optional] Additional Configurations
![Screenshot 2022-02-19 at 12 24 44 PM](https://user-images.githubusercontent.com/86527202/154790565-c0234b2e-ad50-4042-90b6-4f8798f1d585.png) ![Screenshot 2022-02-19 at 12 24 44 PM](https://user-images.githubusercontent.com/86527202/154790565-c0234b2e-ad50-4042-90b6-4f8798f1d585.png)
#### Name your web service #### Name your web service
Pick a name for your NocoDB application. This name will become part of URL subsequently Pick a name for your NocoDB application. This name will become part of URL subsequently
Pick nearest Region for cloud hosting Pick nearest Region for cloud hosting
![Screenshot 2022-02-19 at 12 28 11 PM](https://user-images.githubusercontent.com/86527202/154790567-a6e65e4e-9aa0-4edb-998e-da8803ad6e23.png) ![Screenshot 2022-02-19 at 12 28 11 PM](https://user-images.githubusercontent.com/86527202/154790567-a6e65e4e-9aa0-4edb-998e-da8803ad6e23.png)
#### Finalize and Launch #### Finalize and Launch
- Select hosting plan for your NocoDB application - Select hosting plan for your NocoDB application
- Click "Launch APP" - Click "Launch APP"
![Screenshot 2022-02-19 at 12 29 23 PM](https://user-images.githubusercontent.com/86527202/154790570-62044713-5cca-4d06-82ec-f3cc257218a1.png) ![Screenshot 2022-02-19 at 12 29 23 PM](https://user-images.githubusercontent.com/86527202/154790570-62044713-5cca-4d06-82ec-f3cc257218a1.png)
Application will be build & URL will be live in a minute! The URL will be something like https://simply-nocodb-rsyir.ondigitalocean.app/ Application will be build & URL will be live in a minute! The URL will be something like https://simply-nocodb-rsyir.ondigitalocean.app/
</details> </details>
@ -382,27 +382,27 @@ If your service fails to start, you may check the logs in ECS console or in Clou
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Navigate to App Store #### Navigate to App Store
Log into Cloudron and select App Store Log into Cloudron and select App Store
![image](https://user-images.githubusercontent.com/35857179/194700146-aae90503-a8fd-4bc5-8397-39f0bc279606.png) ![image](https://user-images.githubusercontent.com/35857179/194700146-aae90503-a8fd-4bc5-8397-39f0bc279606.png)
#### Search NocoDB #### Search NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700181-b5303919-70b8-4cf8-bebe-7e75aca601f3.png) ![image](https://user-images.githubusercontent.com/35857179/194700181-b5303919-70b8-4cf8-bebe-7e75aca601f3.png)
#### Click Install #### Click Install
![image](https://user-images.githubusercontent.com/35857179/194700192-d702f5c2-2afa-45c5-9823-4ebe9e141b01.png) ![image](https://user-images.githubusercontent.com/35857179/194700192-d702f5c2-2afa-45c5-9823-4ebe9e141b01.png)
#### Configure NocoDB #### Configure NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700230-c35e934f-bd93-4948-8f31-935483b30571.png) ![image](https://user-images.githubusercontent.com/35857179/194700230-c35e934f-bd93-4948-8f31-935483b30571.png)
#### Go to My App and Launch NocoDB #### Go to My App and Launch NocoDB
![image](https://user-images.githubusercontent.com/35857179/194700464-50098cb1-bf94-42bb-a63a-cc0aad671913.png) ![image](https://user-images.githubusercontent.com/35857179/194700464-50098cb1-bf94-42bb-a63a-cc0aad671913.png)
</details> </details>
@ -411,17 +411,17 @@ If your service fails to start, you may check the logs in ECS console or in Clou
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Login and Click One-Click Apps / Databases #### Login and Click One-Click Apps / Databases
![image](https://user-images.githubusercontent.com/35857179/194701420-7fe5c396-a488-456c-98de-6f2ee1151fc5.png) ![image](https://user-images.githubusercontent.com/35857179/194701420-7fe5c396-a488-456c-98de-6f2ee1151fc5.png)
#### Search NocoDB #### Search NocoDB
![image](https://user-images.githubusercontent.com/35857179/194701537-63e7efc5-013b-4ca9-8659-56e9d536e7d0.png) ![image](https://user-images.githubusercontent.com/35857179/194701537-63e7efc5-013b-4ca9-8659-56e9d536e7d0.png)
#### Configure NocoDB and Deploy #### Configure NocoDB and Deploy
![image](https://user-images.githubusercontent.com/35857179/194701576-19519df5-2aa4-435d-8fc6-7bc684b9cfe1.png) ![image](https://user-images.githubusercontent.com/35857179/194701576-19519df5-2aa4-435d-8fc6-7bc684b9cfe1.png)
</details> </details>
@ -430,15 +430,15 @@ If your service fails to start, you may check the logs in ECS console or in Clou
<details> <details>
<summary>Click to Expand</summary> <summary>Click to Expand</summary>
#### Navigate to Templates #### Navigate to Templates
Go to [Templates](https://railway.app/templates), Search NocoDB and click Deploy Go to [Templates](https://railway.app/templates), Search NocoDB and click Deploy
![image](https://user-images.githubusercontent.com/35857179/194702833-1bea22ee-6dfa-4024-ac27-e33fe56e5500.png) ![image](https://user-images.githubusercontent.com/35857179/194702833-1bea22ee-6dfa-4024-ac27-e33fe56e5500.png)
#### Configure NocoDB and Deploy #### Configure NocoDB and Deploy
![image](https://user-images.githubusercontent.com/35857179/194702960-149393fe-b00f-4d84-9e54-22cb7616ba44.png) ![image](https://user-images.githubusercontent.com/35857179/194702960-149393fe-b00f-4d84-9e54-22cb7616ba44.png)
</details> </details>

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

@ -6216,6 +6216,7 @@ function shouldSkipField(
if (fieldsSet) { if (fieldsSet) {
return !fieldsSet.has(column.title); return !fieldsSet.has(column.title);
} else { } else {
if (column.system && isCreatedOrLastModifiedByCol(column)) return true;
if (!extractPkAndPv) { if (!extractPkAndPv) {
if (!(viewOrTableColumn instanceof Column)) { if (!(viewOrTableColumn instanceof Column)) {
if ( 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') { if (insertObj.meta && typeof insertObj.meta === 'object') {
insertObj.meta = JSON.stringify(insertObj.meta); 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 (column.validate) {
if (typeof column.validate === 'string') if (typeof column.validate === 'string')
insertObj.validate = column.validate; 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]; 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(inputData);
expect(insertedRow).to.deep.include(response); expect(insertedRow).to.deep.include(response);
@ -176,18 +169,10 @@ function baseModelSqlTests() {
const insertedRows: any[] = await baseModelSql.list(); const insertedRows: any[] = await baseModelSql.list();
await baseModelSql.bulkUpdate( await baseModelSql.bulkUpdate(
insertedRows.map( insertedRows.map(({ CreatedAt: _, UpdatedAt: __, ...row }) => ({
({
CreatedAt: _,
UpdatedAt: __,
CreatedBy: ___,
UpdatedBy: ____,
...row
}) => ({
...row, ...row,
Title: `new-${row['Title']}`, Title: `new-${row['Title']}`,
}), })),
),
{ cookie: request }, { cookie: request },
); );

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

@ -52,6 +52,16 @@ function columnTypeSpecificTests() {
uidt: UITypes.LastModifiedTime, uidt: UITypes.LastModifiedTime,
system: true, system: true,
}, },
{
title: 'nc_created_by',
uidt: UITypes.CreatedBy,
system: true,
},
{
title: 'nc_updated_by',
uidt: UITypes.LastModifiedBy,
system: true,
},
]; ];
describe('Qr Code Column', () => { describe('Qr Code Column', () => {
@ -176,7 +186,9 @@ function columnTypeSpecificTests() {
for (let i = 0; i < defaultTableColumns.length; i++) { for (let i = 0; i < defaultTableColumns.length; i++) {
expect(columns[i].title).to.equal(defaultTableColumns[i].title); expect(columns[i].title).to.equal(defaultTableColumns[i].title);
expect(columns[i].uidt).to.equal(defaultTableColumns[i].uidt); 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); 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 // get current date time
const currentDateTime = new Date(); const currentDateTime = new Date();
const d1 = new Date(); const d1 = new Date();
@ -280,13 +292,6 @@ function columnTypeSpecificTests() {
// calculate difference between current date time and stored date time // calculate difference between current date time and stored date time
difference = storedDateTime2.getTime() - storedDateTime1.getTime(); difference = storedDateTime2.getTime() - storedDateTime1.getTime();
expect(difference).to.be.greaterThan(1500); 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 () => { it('Modify record: verify that system fields are RO', async () => {
@ -313,8 +318,8 @@ function columnTypeSpecificTests() {
.send([ .send([
{ {
Id: unfilteredRecords[0].Id, Id: unfilteredRecords[0].Id,
CreatedBy: 'test@example.com', nc_created_by: 'test@example.com',
UpdatedBy: 'test@example.com', nc_updated_by: 'test@example.com',
}, },
]) ])
.expect(400); .expect(400);
@ -341,9 +346,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal( expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedTime, UITypes.CreatedTime,
); );
expect(columns.columns[defaultTableColumns.length].system).to.equal( expect(
false, Boolean(columns.columns[defaultTableColumns.length].system),
); ).to.equal(false);
expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2); expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2);
@ -391,9 +396,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal( expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedBy, UITypes.CreatedBy,
); );
expect(columns.columns[defaultTableColumns.length].system).to.equal( expect(
false, Boolean(columns.columns[defaultTableColumns.length].system),
); ).to.equal(false);
expect(records[0].CreatedBy).to.deep.equal({ expect(records[0].CreatedBy).to.deep.equal({
id: context.user.id, id: context.user.id,
email: context.user.email, email: context.user.email,
@ -406,10 +411,10 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length + 1].uidt).to.equal( expect(columns.columns[defaultTableColumns.length + 1].uidt).to.equal(
UITypes.LastModifiedBy, UITypes.LastModifiedBy,
); );
expect(columns.columns[defaultTableColumns.length + 1].system).to.equal( expect(
false, Boolean(columns.columns[defaultTableColumns.length + 1].system),
); ).to.equal(false);
expect(records[0].UpdatedBy).to.deep.equal(null); expect(records[0].LastModifiedBy).to.deep.equal(null);
// update record should fail // update record should fail
await request(context.app) await request(context.app)
@ -468,9 +473,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal( expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedTime, UITypes.CreatedTime,
); );
expect(columns.columns[defaultTableColumns.length].system).to.equal( expect(
false, Boolean(columns.columns[defaultTableColumns.length].system),
); ).to.equal(false);
expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2); expect(records[0].CreatedAt).to.equal(records[0].CreatedAt2);
}); });
@ -505,9 +510,9 @@ function columnTypeSpecificTests() {
expect(columns.columns[defaultTableColumns.length].uidt).to.equal( expect(columns.columns[defaultTableColumns.length].uidt).to.equal(
UITypes.CreatedBy, UITypes.CreatedBy,
); );
expect(columns.columns[defaultTableColumns.length].system).to.equal( expect(
false, Boolean(columns.columns[defaultTableColumns.length].system),
); ).to.equal(false);
expect(records[0].CreatedBy).to.deep.equal(records[0].CreatedBy); 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 verifyColumnsInRsp = (row, columns: ColumnType[]) => {
const responseColumnsListStr = Object.keys(row).sort().join(','); const responseColumnsListStr = Object.keys(row).sort().join(',');
const expectedColumnsListStr = columns const expectedColumnsListStr = columns
.filter((c) => !(c.system && isCreatedOrLastModifiedByCol(c)))
.map((c) => c.title) .map((c) => c.title)
.sort() .sort()
.join(','); .join(',');
@ -680,12 +681,7 @@ function textBased() {
expect( expect(
verifyColumnsInRsp( verifyColumnsInRsp(
rsp.body.list[0], rsp.body.list[0],
columns.filter( columns.filter((c) => !isCreatedOrLastModifiedTimeCol(c) || !c.system),
(c) =>
(!isCreatedOrLastModifiedTimeCol(c) &&
!isCreatedOrLastModifiedByCol(c)) ||
!c.system,
),
), ),
).to.equal(true); ).to.equal(true);
const filteredArray = rsp.body.list.map((r) => r.SingleLineText); const filteredArray = rsp.body.list.map((r) => r.SingleLineText);
@ -706,9 +702,7 @@ function textBased() {
const displayColumns = columns.filter( const displayColumns = columns.filter(
(c) => (c) =>
c.title !== 'SingleLineText' && c.title !== 'SingleLineText' &&
((!isCreatedOrLastModifiedTimeCol(c) && (!isCreatedOrLastModifiedTimeCol(c) || !c.system),
!isCreatedOrLastModifiedByCol(c)) ||
!c.system),
); );
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
}); });
@ -757,8 +751,7 @@ function textBased() {
(c) => (c) =>
c.title !== 'MultiLineText' && c.title !== 'MultiLineText' &&
c.title !== 'Email' && c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) && !isCreatedOrLastModifiedTimeCol(c),
!isCreatedOrLastModifiedByCol(c),
); );
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
return gridView; return gridView;
@ -778,8 +771,7 @@ function textBased() {
(c) => (c) =>
c.title !== 'MultiLineText' && c.title !== 'MultiLineText' &&
c.title !== 'Email' && c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) && !isCreatedOrLastModifiedTimeCol(c),
!isCreatedOrLastModifiedByCol(c),
); );
expect(rsp.body.pageInfo.totalRows).to.equal(61); expect(rsp.body.pageInfo.totalRows).to.equal(61);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
@ -801,8 +793,7 @@ function textBased() {
(c) => (c) =>
c.title !== 'MultiLineText' && c.title !== 'MultiLineText' &&
c.title !== 'Email' && c.title !== 'Email' &&
!isCreatedOrLastModifiedTimeCol(c) && !isCreatedOrLastModifiedTimeCol(c),
!isCreatedOrLastModifiedByCol(c),
); );
expect(rsp.body.pageInfo.totalRows).to.equal(7); expect(rsp.body.pageInfo.totalRows).to.equal(7);
expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true); expect(verifyColumnsInRsp(rsp.body.list[0], displayColumns)).to.equal(true);
@ -894,9 +885,11 @@ function textBased() {
query: { query: {
offset: 10000, 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 () { it('List: invalid sort, filter, fields', async function () {
@ -2155,12 +2148,14 @@ function linkBased() {
expect(rsp.body.list.length).to.equal(25); expect(rsp.body.list.length).to.equal(25);
rsp.body.list.sort((a, b) => a.Id - b.Id); rsp.body.list.sort((a, b) => a.Id - b.Id);
// paginated response, limit to 25 // paginated response, limit to 25
/* TODO enable this after fix
for (let i = 1; i <= 25; i++) { for (let i = 1; i <= 25; i++) {
expect(rsp.body.list[i - 1]).to.deep.equal({ expect(rsp.body.list[i - 1]).to.deep.equal({
Id: i, Id: i,
Film: `Film ${i}`, Film: `Film ${i}`,
}); });
} }
*/
// verify in Film table // verify in Film table
for (let i = 21; i <= 30; i++) { 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 // Link List: Invalid table ID
if (debugMode) console.log('Link List: Invalid table ID'); if (debugMode) console.log('Link List: Invalid table ID');
await ncAxiosLinkGet({ await ncAxiosLinkGet({
@ -2572,7 +2567,8 @@ function linkBased() {
await ncAxiosLinkGet({ await ncAxiosLinkGet({
...validParams, ...validParams,
query: { ...validParams.query, offset: 9999 }, 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 // Link List: Invalid query parameter - negative limit
@ -2736,7 +2732,7 @@ function linkBased() {
status: 200, status: 200,
}; };
await nestedListTests(validParams); await nestedListTests(validParams, 'bt');
}); });
// Error handling (many-many) // Error handling (many-many)
@ -3062,7 +3058,6 @@ export default function () {
describe('Date based', dateBased); describe('Date based', dateBased);
describe('Link based', linkBased); describe('Link based', linkBased);
describe('User field based', userFieldBased); describe('User field based', userFieldBased);
// based out of Sakila db, for link based tests // based out of Sakila db, for link based tests
describe('General', generalDb); describe('General', generalDb);
} }

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

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

Loading…
Cancel
Save