Browse Source

Merge pull request #5091 from nocodb/feat/5064-rollup-and-lookup-edit-option

feat: Allow editing rollup and lookup column
pull/5119/head
Raju Udava 2 years ago committed by GitHub
parent
commit
d4b5268283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      packages/nc-gui/components/smartsheet/Grid.vue
  2. 4
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  3. 19
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  4. 22
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  5. 4
      packages/nc-gui/components/smartsheet/header/Menu.vue
  6. 1
      packages/nc-gui/lang/ar.json
  7. 1
      packages/nc-gui/lang/bn_IN.json
  8. 1
      packages/nc-gui/lang/cs.json
  9. 1
      packages/nc-gui/lang/da.json
  10. 1
      packages/nc-gui/lang/de.json
  11. 2
      packages/nc-gui/lang/en.json
  12. 1
      packages/nc-gui/lang/es.json
  13. 1
      packages/nc-gui/lang/eu.json
  14. 1
      packages/nc-gui/lang/fa.json
  15. 1
      packages/nc-gui/lang/fi.json
  16. 1
      packages/nc-gui/lang/fr.json
  17. 1
      packages/nc-gui/lang/he.json
  18. 1
      packages/nc-gui/lang/hi.json
  19. 1
      packages/nc-gui/lang/hr.json
  20. 1
      packages/nc-gui/lang/id.json
  21. 1
      packages/nc-gui/lang/it.json
  22. 1
      packages/nc-gui/lang/ja.json
  23. 1
      packages/nc-gui/lang/ko.json
  24. 1
      packages/nc-gui/lang/lv.json
  25. 1
      packages/nc-gui/lang/nl.json
  26. 1
      packages/nc-gui/lang/no.json
  27. 1
      packages/nc-gui/lang/pl.json
  28. 1
      packages/nc-gui/lang/pt.json
  29. 1
      packages/nc-gui/lang/pt_BR.json
  30. 1
      packages/nc-gui/lang/ru.json
  31. 1
      packages/nc-gui/lang/sk.json
  32. 1
      packages/nc-gui/lang/sl.json
  33. 1
      packages/nc-gui/lang/sv.json
  34. 1
      packages/nc-gui/lang/th.json
  35. 1
      packages/nc-gui/lang/tr.json
  36. 1
      packages/nc-gui/lang/uk.json
  37. 1
      packages/nc-gui/lang/vi.json
  38. 1
      packages/nc-gui/lang/zh-Hans.json
  39. 1
      packages/nc-gui/lang/zh-Hant.json
  40. 188
      packages/nocodb/src/lib/meta/api/columnApis.ts
  41. 209
      packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts
  42. 1
      packages/nocodb/src/lib/meta/api/helpers/index.ts
  43. 2
      tests/playwright/tests/columnLookupRollup.spec.ts

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

@ -1056,7 +1056,7 @@ const closeAddColumnDropdown = () => {
position: sticky !important;
left: 80px;
z-index: 5;
@apply border-r-2 border-r-gray-300;
@apply border-r-1 border-r-gray-300;
}
tbody td:nth-child(2) {
@ -1064,7 +1064,7 @@ const closeAddColumnDropdown = () => {
left: 80px;
z-index: 4;
background: white;
@apply shadow-lg border-r-2 border-r-gray-300;
@apply shadow-lg border-r-1 border-r-gray-300;
}
}

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

@ -181,10 +181,10 @@ useEventListener('keydown', (e: KeyboardEvent) => {
<LazySmartsheetColumnDurationOptions v-if="formState.uidt === UITypes.Duration" 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" />
<LazySmartsheetColumnLookupOptions v-if="!isEdit && formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnLookupOptions v-if="formState.uidt === UITypes.Lookup" v-model:value="formState" />
<LazySmartsheetColumnDateOptions v-if="formState.uidt === UITypes.Date" v-model:value="formState" />
<LazySmartsheetColumnDateTimeOptions v-if="formState.uidt === UITypes.DateTime" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="!isEdit && formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnRollupOptions v-if="formState.uidt === UITypes.Rollup" v-model:value="formState" />
<LazySmartsheetColumnLinkedToAnotherRecordOptions
v-if="!isEdit && formState.uidt === UITypes.LinkToAnotherRecord"
v-model:value="formState"

19
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import { getRelationName } from './utils'
@ -14,7 +15,7 @@ const vModel = useVModel(props, 'value', emit)
const meta = $(inject(MetaInj, ref()))
const { setAdditionalValidations, validateInfos, onDataTypeChange } = useColumnCreateStoreOrThrow()
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const { tables } = $(useProject())
@ -49,8 +50,20 @@ const columns = $computed<ColumnType[]>(() => {
if (!selectedTable?.id) {
return []
}
return metas[selectedTable.id].columns.filter((c: ColumnType) => !isSystemColumn(c))
return metas[selectedTable.id].columns.filter((c: ColumnType) => !isSystemColumn(c) && c.id !== vModel.value.id)
})
onMounted(() => {
if (isEdit.value) {
vModel.value.fk_lookup_column_id = vModel.value.colOptions?.fk_lookup_column_id
vModel.value.fk_relation_column_id = vModel.value.colOptions?.fk_relation_column_id
}
})
const onRelationColChange = () => {
vModel.value.fk_lookup_column_id = columns?.[0]?.id
onDataTypeChange()
}
</script>
<template>
@ -60,7 +73,7 @@ const columns = $computed<ColumnType[]>(() => {
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"
@change="onDataTypeChange"
@change="onRelationColChange"
>
<a-select-option v-for="(table, i) of refTables" :key="i" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">

22
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import { onMounted } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, TableType } from 'nocodb-sdk'
import { UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { getRelationName } from './utils'
@ -14,7 +15,7 @@ const vModel = useVModel(props, 'value', emit)
const meta = $(inject(MetaInj, ref()))
const { setAdditionalValidations, validateInfos, onDataTypeChange } = useColumnCreateStoreOrThrow()
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const { tables } = $(useProject())
@ -69,8 +70,23 @@ const columns = $computed(() => {
return []
}
return metas[selectedTable.id].columns.filter((c: ColumnType) => !isVirtualCol(c.uidt as UITypes) && !isSystemColumn(c))
return metas[selectedTable.id].columns.filter(
(c: ColumnType) => !isVirtualCol(c.uidt as UITypes) && (!isSystemColumn(c) || c.pk),
)
})
onMounted(() => {
if (isEdit.value) {
vModel.value.fk_relation_column_id = vModel.value.colOptions?.fk_relation_column_id
vModel.value.fk_rollup_column_id = vModel.value.colOptions?.fk_rollup_column_id
vModel.value.rollup_function = vModel.value.colOptions?.rollup_function
}
})
const onRelationColChange = () => {
vModel.value.fk_rollup_column_id = columns?.[0]?.id
onDataTypeChange()
}
</script>
<template>
@ -80,7 +96,7 @@ const columns = $computed(() => {
<a-select
v-model:value="vModel.fk_relation_column_id"
dropdown-class-name="!w-64 nc-dropdown-relation-table"
@change="onDataTypeChange"
@change="onRelationColChange"
>
<a-select-option v-for="(table, i) of refTables" :key="i" :value="table.col.fk_column_id">
<div class="flex flex-row space-x-0.5 h-full pb-0.5 items-center justify-between">

4
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -285,8 +285,8 @@ const hideField = async () => {
<MdiStar class="text-primary" />
<!-- todo : tooltip -->
<!-- Set as Primary value -->
{{ $t('activity.setPrimary') }}
<!-- Set as Display value -->
{{ $t('activity.setDisplay') }}
</div>
</a-menu-item>

1
packages/nc-gui/lang/ar.json

@ -380,7 +380,6 @@
"renameTable": "إعادة تسمية الجدول",
"deleteTable": "حذف الجدول",
"addField": "إضافة حقل جديد إلى هذا الجدول",
"setPrimary": "تعيين كقيمة أساسية",
"addRow": "إضافة صف جديد",
"saveRow": "حفظ الصف",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/bn_IN.json

@ -380,7 +380,6 @@
"renameTable": "টিল নম পরিবরতন",
"deleteTable": "टबल मि",
"addField": "এই টি নতন কর যত করন",
"setPrimary": "পথমিক মন হিট করন",
"addRow": "নতন সিত করন",
"saveRow": "সিরকষণ করন",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/cs.json

@ -380,7 +380,6 @@
"renameTable": "Přejmenování tabulky",
"deleteTable": "Tabulka Odstranit",
"addField": "Přidání nového pole do této tabulky",
"setPrimary": "Nastavit jako primární hodnotu",
"addRow": "Přidat nový řádek",
"saveRow": "Uložit řádek",
"saveAndExit": "Uložit a odejít",

1
packages/nc-gui/lang/da.json

@ -380,7 +380,6 @@
"renameTable": "Bord omdøb",
"deleteTable": "TABEL DELETE.",
"addField": "Tilføj nyt felt til denne tabel",
"setPrimary": "Indstil som primær værdi",
"addRow": "Tilføj ny række",
"saveRow": "Gem ro",
"saveAndExit": "Gem og afslutning",

1
packages/nc-gui/lang/de.json

@ -380,7 +380,6 @@
"renameTable": "Tabelle umbenennen",
"deleteTable": "Tabelle löschen",
"addField": "Neues Feld zu dieser Tabelle hinzufügen",
"setPrimary": "Als Primärwert festlegen",
"addRow": "Neue Zeile hinzufügen",
"saveRow": "Zeile speichern",
"saveAndExit": "Speichern & Verlassen",

2
packages/nc-gui/lang/en.json

@ -380,7 +380,7 @@
"renameTable": "Table Rename",
"deleteTable": "Table Delete",
"addField": "Add new field to this table",
"setPrimary": "Set as Primary value",
"setDisplay": "Set as Display value",
"addRow": "Add new row",
"saveRow": "Save row",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/es.json

@ -380,7 +380,6 @@
"renameTable": "Cambiar el nombre de la tabla",
"deleteTable": "Borrar tabla",
"addField": "Añadir nuevo campo a esta tabla",
"setPrimary": "Establecido como clave primaria",
"addRow": "Añadir nueva fila",
"saveRow": "Grabar la fila",
"saveAndExit": "Guardar y salir",

1
packages/nc-gui/lang/eu.json

@ -380,7 +380,6 @@
"renameTable": "Table Rename",
"deleteTable": "Table Delete",
"addField": "Add new field to this table",
"setPrimary": "Set as Primary value",
"addRow": "Add new row",
"saveRow": "Save row",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/fa.json

@ -380,7 +380,6 @@
"renameTable": "تغییر نام جدول",
"deleteTable": "حذف جدول",
"addField": "اضافه کردن فیلد جدید به این جدول",
"setPrimary": "تنظیم به عنوان مقدار اولیه",
"addRow": "اضافه کردن ردیف جدید",
"saveRow": "دخیره ردیف",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/fi.json

@ -380,7 +380,6 @@
"renameTable": "Taulukko uudelleen",
"deleteTable": "Taulukko poistaa",
"addField": "Lisää uusi kenttä tähän taulukkoon",
"setPrimary": "Aseta ensisijainen arvo",
"addRow": "Lisää uusi rivi",
"saveRow": "Tallenna rivi",
"saveAndExit": "Tallenna & poistu",

1
packages/nc-gui/lang/fr.json

@ -380,7 +380,6 @@
"renameTable": "Renommer le tableau",
"deleteTable": "Supprimer le tableau",
"addField": "Ajouter un nouveau champ à ce tableau",
"setPrimary": "Définir comme valeur primaire",
"addRow": "Ajouter une nouvelle ligne",
"saveRow": "Enregistrer la ligne",
"saveAndExit": "Enregistrer et quitter",

1
packages/nc-gui/lang/he.json

@ -380,7 +380,6 @@
"renameTable": "שולחן שינוי שם",
"deleteTable": "טבלה מחיקה",
"addField": "הוסף שדה חדש לטבלה זו",
"setPrimary": "להגדיר כערך ראשי",
"addRow": "הוסף שורה חדשה",
"saveRow": "שמור שורה",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/hi.json

@ -380,7 +380,6 @@
"renameTable": "तिम",
"deleteTable": "टबल मि",
"addField": "इस ति नयड ज",
"setPrimary": "पथमिक मय कप मट कर",
"addRow": "नई पि",
"saveRow": "पि सह",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/hr.json

@ -380,7 +380,6 @@
"renameTable": "Preimenovati stolom",
"deleteTable": "Obriši tablicu",
"addField": "Dodajte novo polje na ovu tablicu",
"setPrimary": "Postavite kao primarnu vrijednost",
"addRow": "Dodaj novi red",
"saveRow": "Spremanje retka",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/id.json

@ -380,7 +380,6 @@
"renameTable": "Ganti nama meja",
"deleteTable": "Table Delete.",
"addField": "Tambahkan bidang baru ke tabel ini",
"setPrimary": "Tetapkan sebagai nilai utama",
"addRow": "Tambahkan baris baru",
"saveRow": "Hemat Baris",
"saveAndExit": "Simpan & Keluar",

1
packages/nc-gui/lang/it.json

@ -380,7 +380,6 @@
"renameTable": "Rinomina tabella",
"deleteTable": "Elimina tabella",
"addField": "Aggiungi un nuovo campo a questa tabella",
"setPrimary": "Impostare come valore primario",
"addRow": "Aggiungi nuova riga",
"saveRow": "Salva riga",
"saveAndExit": "Salvare e uscire",

1
packages/nc-gui/lang/ja.json

@ -380,7 +380,6 @@
"renameTable": "テーブル名の変更",
"deleteTable": "テーブルを削除",
"addField": "新しいフィールドを追加",
"setPrimary": "プライマリ値として設定",
"addRow": "行を追加",
"saveRow": "行を保存",
"saveAndExit": "保存して終了",

1
packages/nc-gui/lang/ko.json

@ -380,7 +380,6 @@
"renameTable": "테이블 이름 바꾸기",
"deleteTable": "테이블 삭제",
"addField": "테이블에 새 필드 추가",
"setPrimary": "Primary value로 설정",
"addRow": "행 추가",
"saveRow": "행 저장",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/lv.json

@ -380,7 +380,6 @@
"renameTable": "Tabulas pārdēvēšana",
"deleteTable": "Tabulas dzēšana",
"addField": "Jauna lauka pievienošana",
"setPrimary": "Uzstādīt kā primāro atslēgu",
"addRow": "Pievienot ierakstu",
"saveRow": "Saglabāt ierakstu",
"saveAndExit": "Saglabāt un iziet",

1
packages/nc-gui/lang/nl.json

@ -380,7 +380,6 @@
"renameTable": "Tabel hernoemen",
"deleteTable": "Tabel verwijderen",
"addField": "Voeg nieuw veld toe aan deze tabel",
"setPrimary": "Instellen als primaire waarde",
"addRow": "Nieuwe rij toevoegen",
"saveRow": "Sla rij op",
"saveAndExit": "Opslaan en afsluiten",

1
packages/nc-gui/lang/no.json

@ -380,7 +380,6 @@
"renameTable": "Tabell omdøpe",
"deleteTable": "Bordet slett",
"addField": "Legg til nytt felt i denne tabellen",
"setPrimary": "Sett som primærverdi",
"addRow": "Legg til ny rad",
"saveRow": "Lagre rad",
"saveAndExit": "Save & Exit",

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

@ -380,7 +380,6 @@
"renameTable": "Zmień nazwę tabeli.",
"deleteTable": "Usuń tabelę",
"addField": "Dodaj nowe pole do tej tabeli",
"setPrimary": "Ustaw jako wartość podstawowa",
"addRow": "Dodaj nowy rząd",
"saveRow": "Zapisz wiersz",
"saveAndExit": "Zapisz i wyjdź",

1
packages/nc-gui/lang/pt.json

@ -380,7 +380,6 @@
"renameTable": "Tabela Renomear",
"deleteTable": "Tabela Delete.",
"addField": "Adicionar novo campo a esta tabela",
"setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha",
"saveRow": "Salvar linha",
"saveAndExit": "Salvar & Sair",

1
packages/nc-gui/lang/pt_BR.json

@ -380,7 +380,6 @@
"renameTable": "Tabela Renomear",
"deleteTable": "Tabela Delete.",
"addField": "Adicionar novo campo a esta tabela",
"setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha",
"saveRow": "Salvar linha",
"saveAndExit": "Salvar & Sair",

1
packages/nc-gui/lang/ru.json

@ -380,7 +380,6 @@
"renameTable": "Переименовать таблицу",
"deleteTable": "Удалить таблицу",
"addField": "Добавить новое поле в эту таблицу",
"setPrimary": "Установить в качестве основного значения",
"addRow": "Добавить новую строку",
"saveRow": "Сохранить строку",
"saveAndExit": "Сохранить и выйти",

1
packages/nc-gui/lang/sk.json

@ -380,7 +380,6 @@
"renameTable": "Premenovanie tabuľky",
"deleteTable": "Tabuľka Vymazať",
"addField": "Pridanie nového poľa do tejto tabuľky",
"setPrimary": "Nastaviť ako primárnu hodnotu",
"addRow": "Pridanie nového riadku",
"saveRow": "Uložiť riadok",
"saveAndExit": "Uložiť a ukončiť",

1
packages/nc-gui/lang/sl.json

@ -380,7 +380,6 @@
"renameTable": "Preimenuj tabele",
"deleteTable": "Tabela Delete.",
"addField": "V to tabelo dodajte novo polje",
"setPrimary": "Kot primarna vrednost",
"addRow": "Dodaj novo vrstico",
"saveRow": "Shrani vrstico",
"saveAndExit": "Shranjevanje in izhod",

1
packages/nc-gui/lang/sv.json

@ -380,7 +380,6 @@
"renameTable": "Bordsbyte",
"deleteTable": "Bord radera",
"addField": "Lägg till nytt fält till den här tabellen",
"setPrimary": "Ange som primärt värde",
"addRow": "Lägg till ny rad",
"saveRow": "Spara rad",
"saveAndExit": "Spara och avsluta",

1
packages/nc-gui/lang/th.json

@ -380,7 +380,6 @@
"renameTable": "ตารางเปลยนชอ",
"deleteTable": "ลบตาราง",
"addField": "เพมฟลดใหมลงในตารางน",
"setPrimary": "ตงคาเปนคาปฐมภ",
"addRow": "เพมแถวใหม",
"saveRow": "บนทกแถว",
"saveAndExit": "Save & Exit",

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

@ -380,7 +380,6 @@
"renameTable": "Tabloyu Yeniden Adlandır",
"deleteTable": "Tabloyu Sil",
"addField": "Tabloya yeni alan ekle",
"setPrimary": "Birincil değer yap",
"addRow": "Yeni satır ekle",
"saveRow": "Satırı kaydet",
"saveAndExit": "Kaydet ve Çık",

1
packages/nc-gui/lang/uk.json

@ -380,7 +380,6 @@
"renameTable": "Перейменувати таблицю",
"deleteTable": "Видалити таблицю",
"addField": "Додати нове поле до цієї таблиці",
"setPrimary": "Встановити як основне значення",
"addRow": "Додати новий рядок",
"saveRow": "Зберегти рядок",
"saveAndExit": "Зберегти та вийти",

1
packages/nc-gui/lang/vi.json

@ -380,7 +380,6 @@
"renameTable": "Đổi tên bảng.",
"deleteTable": "Bảng xóa",
"addField": "Thêm trường mới vào bảng này",
"setPrimary": "Đặt dưới dạng giá trị chính",
"addRow": "Thêm hàng mới",
"saveRow": "Lưu hàng.",
"saveAndExit": "Save & Exit",

1
packages/nc-gui/lang/zh-Hans.json

@ -380,7 +380,6 @@
"renameTable": "重命名表格",
"deleteTable": "删除表格",
"addField": "添加新字段",
"setPrimary": "设置为主要值",
"addRow": "添加新行",
"saveRow": "保存行",
"saveAndExit": "保存并退出",

1
packages/nc-gui/lang/zh-Hant.json

@ -380,7 +380,6 @@
"renameTable": "表重命名",
"deleteTable": "表刪除",
"addField": "將新字段添加到此表",
"setPrimary": "設置為主要值",
"addRow": "新增行",
"saveRow": "儲存行",
"saveAndExit": "儲存並結束",

188
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -6,7 +6,6 @@ import Column from '../../models/Column';
import { Tele } from 'nc-help';
import validateParams from '../helpers/validateParams';
import { customAlphabet } from 'nanoid';
import LinkToAnotherRecordColumn from '../../models/LinkToAnotherRecordColumn';
import {
getUniqueColumnAliasName,
@ -19,9 +18,7 @@ import {
isVirtualCol,
LinkToAnotherColumnReqType,
LinkToAnotherRecordType,
LookupColumnReqType,
RelationTypes,
RollupColumnReqType,
substituteColumnAliasWithIdInFormula,
substituteColumnIdWithAliasInFormula,
TableType,
@ -41,8 +38,14 @@ import FormulaColumn from '../../models/FormulaColumn';
import KanbanView from '../../models/KanbanView';
import { MetaTable } from '../../utils/globals';
import formulaQueryBuilderv2 from '../../db/sql-data-mapper/lib/sql/formulav2/formulaQueryBuilderv2';
const randomID = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 10);
import {
createHmAndBtColumn,
generateFkName,
randomID,
validateLookupPayload,
validateRequiredField,
validateRollupPayload,
} from './helpers';
export enum Altered {
NEW_COLUMN = 1,
@ -50,73 +53,6 @@ export enum Altered {
UPDATE_COLUMN = 8,
}
// generate unique foreign key constraint name for foreign key
const generateFkName = (parent: TableType, child: TableType) => {
// generate a unique constraint name by taking first 10 chars of parent and child table name (by replacing all non word chars with _)
// and appending a random string of 15 chars maximum length.
// In database constraint name can be upto 64 chars and here we are generating a name of maximum 40 chars
const constraintName = `fk_${parent.table_name
.replace(/\W+/g, '_')
.slice(0, 10)}_${child.table_name
.replace(/\W+/g, '_')
.slice(0, 10)}_${randomID(15)}`;
return constraintName;
};
async function createHmAndBtColumn(
child: Model,
parent: Model,
childColumn: Column,
type?: RelationTypes,
alias?: string,
fkColName?: string,
virtual = false,
isSystemCol = false
) {
// save bt column
{
const title = getUniqueColumnAliasName(
await child.getColumns(),
type === 'bt' ? alias : `${parent.title}`
);
await Column.insert<LinkToAnotherRecordColumn>({
title,
fk_model_id: child.id,
// ref_db_alias
uidt: UITypes.LinkToAnotherRecord,
type: 'bt',
// db_type:
fk_child_column_id: childColumn.id,
fk_parent_column_id: parent.primaryKey.id,
fk_related_model_id: parent.id,
virtual,
system: isSystemCol,
fk_index_name: fkColName,
});
}
// save hm column
{
const title = getUniqueColumnAliasName(
await parent.getColumns(),
type === 'hm' ? alias : `${child.title} List`
);
await Column.insert({
title,
fk_model_id: parent.id,
uidt: UITypes.LinkToAnotherRecord,
type: 'hm',
fk_child_column_id: childColumn.id,
fk_parent_column_id: parent.primaryKey.id,
fk_related_model_id: child.id,
virtual,
system: isSystemCol,
fk_index_name: fkColName,
});
}
}
export async function columnGet(req: Request, res: Response) {
res.json(await Column.get({ colId: req.params.columnId }));
}
@ -153,49 +89,7 @@ export async function columnAdd(
switch (colBody.uidt) {
case UITypes.Rollup:
{
validateParams(
[
'title',
'fk_relation_column_id',
'fk_rollup_column_id',
'rollup_function',
],
req.body
);
const relation = await (
await Column.get({
colId: (req.body as RollupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
if (!relation) {
throw new Error('Relation column not found');
}
let relatedColumn: Column;
switch (relation.type) {
case 'hm':
relatedColumn = await Column.get({
colId: relation.fk_child_column_id,
});
break;
case 'mm':
case 'bt':
relatedColumn = await Column.get({
colId: relation.fk_parent_column_id,
});
break;
}
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) =>
c.id === (req.body as RollupColumnReqType).fk_rollup_column_id
)
)
throw new Error('Rollup column not found in related table');
await validateRollupPayload(req.body);
await Column.insert({
...colBody,
@ -205,44 +99,7 @@ export async function columnAdd(
break;
case UITypes.Lookup:
{
validateParams(
['title', 'fk_relation_column_id', 'fk_lookup_column_id'],
req.body
);
const relation = await (
await Column.get({
colId: (req.body as LookupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
if (!relation) {
throw new Error('Relation column not found');
}
let relatedColumn: Column;
switch (relation.type) {
case 'hm':
relatedColumn = await Column.get({
colId: relation.fk_child_column_id,
});
break;
case 'mm':
case 'bt':
relatedColumn = await Column.get({
colId: relation.fk_parent_column_id,
});
break;
}
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) =>
c.id === (req.body as LookupColumnReqType).fk_lookup_column_id
)
)
throw new Error('Lookup column not found in related table');
await validateLookupPayload(req.body);
await Column.insert({
...colBody,
@ -252,7 +109,6 @@ export async function columnAdd(
break;
case UITypes.LinkToAnotherRecord:
// case UITypes.ForeignKey:
{
validateParams(['parentId', 'childId', 'type'], req.body);
@ -753,6 +609,29 @@ export async function columnSetAsPrimary(req: Request, res: Response) {
res.json(await Model.updatePrimaryColumn(column.fk_model_id, column.id));
}
async function updateRollupOrLookup(colBody: any, column: Column<any>) {
if (
UITypes.Lookup === column.uidt &&
validateRequiredField(colBody, [
'fk_lookup_column_id',
'fk_relation_column_id',
])
) {
await validateLookupPayload(colBody, column.id);
await Column.update(column.id, colBody);
} else if (
UITypes.Rollup === column.uidt &&
validateRequiredField(colBody, [
'fk_relation_column_id',
'fk_rollup_column_id',
'rollup_function',
])
) {
await validateRollupPayload(colBody);
await Column.update(column.id, colBody);
}
}
export async function columnUpdate(req: Request, res: Response<TableType>) {
const column = await Column.get({ colId: req.params.columnId });
@ -824,6 +703,7 @@ export async function columnUpdate(req: Request, res: Response<TableType>) {
title: colBody.title,
});
}
await updateRollupOrLookup(colBody, column);
} else {
NcError.notImplemented(
`Updating ${colBody.uidt} => ${colBody.uidt} is not implemented`

209
packages/nocodb/src/lib/meta/api/helpers/columnHelpers.ts

@ -0,0 +1,209 @@
import { customAlphabet } from 'nanoid';
import {
ColumnReqType,
LinkToAnotherRecordType,
LookupColumnReqType,
RelationTypes,
RollupColumnReqType,
TableType,
UITypes,
} from 'nocodb-sdk';
import Column from '../../../models/Column';
import LinkToAnotherRecordColumn from '../../../models/LinkToAnotherRecordColumn';
import LookupColumn from '../../../models/LookupColumn';
import Model from '../../../models/Model';
import { getUniqueColumnAliasName } from '../../helpers/getUniqueName';
import validateParams from '../../helpers/validateParams';
export const randomID = customAlphabet(
'1234567890abcdefghijklmnopqrstuvwxyz_',
10
);
export async function createHmAndBtColumn(
child: Model,
parent: Model,
childColumn: Column,
type?: RelationTypes,
alias?: string,
fkColName?: string,
virtual = false,
isSystemCol = false
) {
// save bt column
{
const title = getUniqueColumnAliasName(
await child.getColumns(),
type === 'bt' ? alias : `${parent.title}`
);
await Column.insert<LinkToAnotherRecordColumn>({
title,
fk_model_id: child.id,
// ref_db_alias
uidt: UITypes.LinkToAnotherRecord,
type: 'bt',
// db_type:
fk_child_column_id: childColumn.id,
fk_parent_column_id: parent.primaryKey.id,
fk_related_model_id: parent.id,
virtual,
system: isSystemCol,
fk_col_name: fkColName,
fk_index_name: fkColName,
});
}
// save hm column
{
const title = getUniqueColumnAliasName(
await parent.getColumns(),
type === 'hm' ? alias : `${child.title} List`
);
await Column.insert({
title,
fk_model_id: parent.id,
uidt: UITypes.LinkToAnotherRecord,
type: 'hm',
fk_child_column_id: childColumn.id,
fk_parent_column_id: parent.primaryKey.id,
fk_related_model_id: child.id,
virtual,
system: isSystemCol,
fk_col_name: fkColName,
fk_index_name: fkColName,
});
}
}
export async function validateRollupPayload(
payload: ColumnReqType & { uidt: UITypes }
) {
validateParams(
[
'title',
'fk_relation_column_id',
'fk_rollup_column_id',
'rollup_function',
],
payload
);
const relation = await (
await Column.get({
colId: (payload as RollupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
if (!relation) {
throw new Error('Relation column not found');
}
let relatedColumn: Column;
switch (relation.type) {
case 'hm':
relatedColumn = await Column.get({
colId: relation.fk_child_column_id,
});
break;
case 'mm':
case 'bt':
relatedColumn = await Column.get({
colId: relation.fk_parent_column_id,
});
break;
}
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) => c.id === (payload as RollupColumnReqType).fk_rollup_column_id
)
)
throw new Error('Rollup column not found in related table');
}
export async function validateLookupPayload(
payload: ColumnReqType & { uidt: UITypes },
columnId?: string
) {
validateParams(
['title', 'fk_relation_column_id', 'fk_lookup_column_id'],
payload
);
// check for circular reference
if (columnId) {
let lkCol: LookupColumn | LookupColumnReqType =
payload as LookupColumnReqType;
while (lkCol) {
// check if lookup column is same as column itself
if (columnId === lkCol.fk_lookup_column_id)
throw new Error('Circular lookup reference not allowed');
lkCol = await Column.get({ colId: lkCol.fk_lookup_column_id }).then(
(c: Column) => {
if (c.uidt === 'Lookup') {
return c.getColOptions<LookupColumn>();
}
return null;
}
);
}
}
const relation = await (
await Column.get({
colId: (payload as LookupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
if (!relation) {
throw new Error('Relation column not found');
}
let relatedColumn: Column;
switch (relation.type) {
case 'hm':
relatedColumn = await Column.get({
colId: relation.fk_child_column_id,
});
break;
case 'mm':
case 'bt':
relatedColumn = await Column.get({
colId: relation.fk_parent_column_id,
});
break;
}
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) => c.id === (payload as LookupColumnReqType).fk_lookup_column_id
)
)
throw new Error('Lookup column not found in related table');
}
export const validateRequiredField = (
payload: Record<string, any>,
requiredProps: string[]
) => {
return requiredProps.every(
(prop) =>
prop in payload && payload[prop] !== undefined && payload[prop] !== null
);
};
// generate unique foreign key constraint name for foreign key
export const generateFkName = (parent: TableType, child: TableType) => {
// generate a unique constraint name by taking first 10 chars of parent and child table name (by replacing all non word chars with _)
// and appending a random string of 15 chars maximum length.
// In database constraint name can be upto 64 chars and here we are generating a name of maximum 40 chars
const constraintName = `fk_${parent.table_name
.replace(/\W+/g, '_')
.slice(0, 10)}_${child.table_name
.replace(/\W+/g, '_')
.slice(0, 10)}_${randomID(15)}`;
return constraintName;
};

1
packages/nocodb/src/lib/meta/api/helpers/index.ts

@ -1,3 +1,4 @@
import { populateMeta } from './populateMeta';
export * from './columnHelpers';
export { populateMeta };

2
tests/playwright/tests/columnLookupRollup.spec.ts

@ -41,7 +41,7 @@ test.describe('Virtual columns', () => {
title: 'Rollup',
type: 'Rollup',
childTable: 'City List',
childColumn: 'City',
childColumn: 'CityId',
rollupType: 'count',
});
for (let i = 0; i < pinCode.length; i++) {

Loading…
Cancel
Save