From 7fae62f0ad3414cc108d63cf0a4d7c8f69433ad4 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 25 Apr 2024 05:55:18 +0000 Subject: [PATCH 01/46] test: fix undo redo cal timeout --- .../Dashboard/Calendar/CalendarWeekDateTime.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts b/tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts index ee146186e7..f8ec7cf31d 100644 --- a/tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts +++ b/tests/playwright/pages/Dashboard/Calendar/CalendarWeekDateTime.ts @@ -48,12 +48,18 @@ export class CalendarWeekDateTimePage extends BasePage { const day = this.get().getByTestId('nc-calendar-week-day').nth(dayIndex); const hour = day.getByTestId('nc-calendar-week-hour').nth(hourIndex); - await hour.click({ - force: true, - position: { - x: -1, - y: -1, - }, + + await this.waitForResponse({ + uiAction: () => + hour.click({ + force: true, + position: { + x: -1, + y: -1, + }, + }), + requestUrlPathToMatch: '/api/v1/db/data/noco', + httpMethodsToMatch: ['GET'], }); } } From a6aff612b9a3a307300a4f35f415213a2259baa5 Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Thu, 25 Apr 2024 05:55:18 +0000 Subject: [PATCH 02/46] fix: group-by flaky test --- tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts index e6e7f73e55..c47ae79cc5 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts +++ b/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts @@ -49,6 +49,7 @@ export class ToolbarGroupByPage extends BasePage { .locator(`div[label="${title}"]`) .last() .click(); + await this.rootPage.locator('.nc-sort-dir-select').nth(index).waitFor({ state: 'visible' }); await this.rootPage.locator('.nc-sort-dir-select').nth(index).click(); await this.rootPage .locator('.nc-dropdown-sort-dir') From 03331937ce9a1ceb7007469c36a93c2b33fb0867 Mon Sep 17 00:00:00 2001 From: navi Date: Fri, 26 Apr 2024 07:51:32 +0100 Subject: [PATCH 03/46] New translations en.json (Turkish) (#8348) --- packages/nc-gui/lang/tr.json | 188 +++++++++++++++++------------------ 1 file changed, 94 insertions(+), 94 deletions(-) diff --git a/packages/nc-gui/lang/tr.json b/packages/nc-gui/lang/tr.json index 7f51be5bc1..1c8466351d 100644 --- a/packages/nc-gui/lang/tr.json +++ b/packages/nc-gui/lang/tr.json @@ -1,45 +1,45 @@ { "dashboards": { - "create_new_dashboard_project": "Create New Interface", - "connect_data_sources": "Connect data sources", - "alert": "Alert", - "alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", - "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", - "create_interface": "Create interface", - "project_name": "Base Name", - "connect": "Connect", + "create_new_dashboard_project": "Yeni Arayüz Oluştur", + "connect_data_sources": "Yeni kaynağa bağlan", + "alert": "Uyarı", + "alert-message": "Hiçbir veritabanına bağlanmadı. Arayüz oluşturmak için veritabanlarına bağlanın. Bu adımı atlayın ve veritabanlarını daha sonra proje ana sayfasından ekleyin.", + "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Bu arayüze bağlamak için bir veritabanı seçin.", + "create_interface": "Arayüz Oluştur", + "project_name": "Proje Adı", + "connect": "Bağlan", "buttonActionTypes": { - "open_external_url": "Open external link", - "delete_record": "Delete record", - "update_record": "Update record", - "open_layout": "Open layout" + "open_external_url": "Dış bağlantıyı aç", + "delete_record": "Kaydı sil", + "update_record": "Kaydı güncelle", + "open_layout": "Düzeni aç" }, "widgets": { - "static_text": "Text", - "chart": "Chart", - "table": "Table", - "image": "Image", - "map": "Map", - "button": "Button", - "number": "Number", - "bar_chart": "Bar Chart", - "line_chart": "Line Chart", - "area_chart": "Area Chart", - "pie_chart": "Pie Chart", - "donut_chart": "Donut Chart", - "scatter_plot": "Scatter Plot", - "bubble_chart": "Bubble Chart", - "radar_chart": "Radar Chart", - "polar_area_chart": "Polar Area Chart", - "radial_bar_chart": "Radial Bar Chart", - "heatmap_chart": "Heatmap Chart", - "treemap_chart": "Treemap Chart", - "box_plot_chart": "Box Plot Chart", - "candlestick_chart": "Candlestick Chart" + "static_text": "Metin", + "chart": "Grafik", + "table": "Tablo", + "image": "Görüntü", + "map": "Harita", + "button": "Buton", + "number": "Sayı", + "bar_chart": "Çubuk Grafiği", + "line_chart": "Çizgi Grafiği", + "area_chart": "Alan Grafiği", + "pie_chart": "Pasta Grafiği", + "donut_chart": "Halka Grafiği", + "scatter_plot": "Dağılım Grafiği", + "bubble_chart": "Kabarcık Grafiği", + "radar_chart": "Radar Grafiği", + "polar_area_chart": "Polar Alan Grafiği", + "radial_bar_chart": "Radyal Çubuk Grafiği", + "heatmap_chart": "Isı Haritası Grafiği", + "treemap_chart": "Dizin Grafiği", + "box_plot_chart": "Kutu Matris Grifiği", + "candlestick_chart": "Şamdan Grafiği" } }, "general": { - "quit": "Quit", + "quit": "Çıkış", "home": "Ana Sayfa", "load": "Yükle", "open": "Aç", @@ -47,29 +47,29 @@ "yes": "Evet", "no": "Hayır", "ok": "Tamam", - "back": "Back", + "back": "Geri", "and": "Ve", "or": "Veya", "add": "Ekle", "edit": "Düzenle", - "link": "Link", - "links": "Links", + "link": "Bağlantı", + "links": "Bağlantılar", "remove": "Sil", - "import": "Import", - "logout": "Log Out", - "empty": "Empty", - "changeIcon": "Change Icon", + "import": "İçe aktar", + "logout": "Oturumu Kapat", + "empty": "Boş", + "changeIcon": "Simgeyi Değiştir", "save": "Kaydet", - "available": "Available", - "abort": "Abort", - "saving": "Saving", + "available": "Uygun", + "abort": "İptal", + "saving": "Kaydediliyor", "cancel": "İptal", "null": "Null", - "escape": "Escape", + "escape": "Esc", "hex": "Hex", - "clear": "Clear", + "clear": "Temizle", "slack": "Slack", - "comment": "Comment", + "comment": "Yorum", "microsoftTeams": "Microsoft Teams", "discord": "Discord", "matterMost": "Mattermost", @@ -183,32 +183,32 @@ "count": "Count", "countDistinct": "Count Distinct", "sumDistinct": "Sum Distinct", - "avgDistinct": "Avg Distinct", - "join": "Join", - "options": "Options", - "primaryValue": "Primary Value", - "useSurveyMode": "Use Survey Mode", + "avgDistinct": "Farklı Değerlerin Ort", + "join": "Bağla", + "options": "Seçenekler", + "primaryValue": "Birincil Değer", + "useSurveyMode": "Anket Modunu Kullan", "shift": "Shift", "enter": "Enter", - "seconds": "Seconds", - "paste": "Paste", - "restore": "Restore", - "replace": "Replace", - "banner": "Banner", + "seconds": "Saniye", + "paste": "Yapıştır", + "restore": "Geri Yükle", + "replace": "Değiştir", + "banner": "Afiş", "logo": "Logo", - "dropdown": "Dropdown", - "list": "List", - "apply": "Apply", - "text": "Text", - "appearance": "Appearance" + "dropdown": "Açılır menü", + "list": "Liste", + "apply": "Uygula", + "text": "Metin", + "appearance": "Görünüm" }, "objects": { - "day": "Day", - "week": "Week", - "month": "Month", - "year": "Year", - "workspace": "Workspace", - "workspaces": "Workspaces", + "day": "Gün", + "week": "Hafta", + "month": "Ay", + "year": "Yıl", + "workspace": "Çalışma Alanı", + "workspaces": "Çalışma Alanları", "project": "Proje", "projects": "Projeler", "table": "Tablo", @@ -225,7 +225,7 @@ "webhooks": "Webhooklar", "view": "Görünüm", "views": "Görünümler", - "sidebar": "Sidebar", + "sidebar": "Kenar Çubuğu", "viewType": { "grid": "Tablo", "gallery": "Galeri", @@ -238,27 +238,27 @@ "users": "Kullanıcılar", "role": "Rol", "roles": "Roller", - "developer": "Developer", + "developer": "Geliştirici", "roleType": { "owner": "Sahip", "creator": "Yaratıcı", "editor": "Editör", "commenter": "Yorumcu", "viewer": "İzleyici", - "noaccess": "No Access", - "superAdmin": "Super Admin", + "noaccess": "Erişim Yok", + "superAdmin": "Süper Admin", "orgLevelCreator": "Organizasyon Seviyesi Oluşturucu", "orgLevelViewer": "Organizasyon Seviyesi Görüntüleyici" }, "sqlVIew": "SQL Görünümü", - "rowHeight": "Record Height", + "rowHeight": "Sütun Yüksekliği", "heightClass": { - "short": "Short", - "medium": "Medium", - "tall": "Tall", - "extra": "Extra" + "short": "Kısa", + "medium": "Orta", + "tall": "Uzun", + "extra": "Ekstra" }, - "externalDb": "External Database" + "externalDb": "Harici Veritabanı" }, "datatype": { "ID": "KIMLIK", @@ -313,22 +313,22 @@ "isNotNull": "null değil" }, "title": { - "sso": "Authentication (SSO)", - "docs": "Docs", + "sso": "Kimlik Doğrulama (SSO)", + "docs": "Dokümantasyon", "forum": "Forum", - "parameter": "Parameter", - "headers": "Headers", - "parameterName": "Parameter Name", - "currencyLocale": "Currency Locale", - "currencyCode": "Currency Code", - "searchMembers": "Search Members", - "noMembersFound": "No members found", - "dateJoined": "Date Joined", - "tokenName": "Token name", - "inDesktop": "in Desktop", - "rowData": "Record data", - "creator": "Creator", - "qrCode": "QR Code", + "parameter": "Parametre", + "headers": "Üstbilgi", + "parameterName": "Parametre Adı", + "currencyLocale": "Para Birimi Dili", + "currencyCode": "Para Birimi", + "searchMembers": "Üyeleri Bul", + "noMembersFound": "Üye bulunamadı", + "dateJoined": "Katılım Tarihi", + "tokenName": "Token adı", + "inDesktop": "Masaüstü", + "rowData": "Kayıt verisi", + "creator": "Yaratıcı", + "qrCode": "QR Kod", "termsOfService": "Terms of Service", "updateSelectedRows": "Update Selected Records", "noFiltersAdded": "No filters added", @@ -417,7 +417,7 @@ "resetPasswordMenu": "Reset Password", "tokens": "Tokens", "userManagement": "User Management", - "accountManagement": "Account management", + "accountManagement": "Hesap yönetimi", "licence": "Licence", "allowAllMimeTypes": "Allow All Mime Types", "defaultView": "Default View", From 994a0d7905ec79af3658b977c96c8fb7cbf4606c Mon Sep 17 00:00:00 2001 From: Raju Udava <86527202+dstala@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:26:24 +0530 Subject: [PATCH 04/46] Nc test/flaky fix (#8349) * fix: flaky test * Update index.ts * fix: flaky with addNewRow- replace with APIs * fix: groupby flaky * test: retry attempt for calendar sidebar card count * test: use x button instead of escape to close expand modal * test: fix selected cell count retry * test: fix flaky for cmd K * fix: increase wait time after form submission --- .../Dashboard/Calendar/CalendarSideMenu.ts | 14 +++++--- .../pages/Dashboard/ExpandedForm/index.ts | 2 +- .../playwright/pages/Dashboard/Grid/index.ts | 8 +++++ .../pages/Dashboard/common/Toolbar/Groupby.ts | 13 +++++-- tests/playwright/pages/SharedForm/index.ts | 2 +- .../db/columns/columnMultiSelect.spec.ts | 35 +++++++++++++++---- .../tests/db/features/command.spec.ts | 2 ++ .../tests/db/general/cellSelection.spec.ts | 16 +++++---- 8 files changed, 71 insertions(+), 21 deletions(-) diff --git a/tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts b/tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts index e11d08bd4e..c3202e5fae 100644 --- a/tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts +++ b/tests/playwright/pages/Dashboard/Calendar/CalendarSideMenu.ts @@ -30,10 +30,16 @@ export class CalendarSideMenuPage extends BasePage { } async verifySideBarRecords({ records }: { records: string[] }) { - const sideBar = this.get().getByTestId('nc-calendar-side-menu-list'); - - const sideBarRecords = await sideBar.getByTestId('nc-sidebar-record-card'); - + let attempts = 0; + let sideBarRecords: Locator; + while (attempts++ < 5) { + const sideBar = this.get().getByTestId('nc-calendar-side-menu-list'); + sideBarRecords = sideBar.getByTestId('nc-sidebar-record-card'); + + if ((await sideBarRecords.count()) === records.length) break; + // wait for records to load + await this.rootPage.waitForTimeout(200 * attempts); + } await expect(sideBarRecords).toHaveCount(records.length); for (let i = 0; i < records.length; i++) { diff --git a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts index 1a76a78d59..8ad6d110a1 100644 --- a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts +++ b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts @@ -129,7 +129,7 @@ export class ExpandedFormPage extends BasePage { await this.rootPage.locator('[data-testid="grid-load-spinner"]').waitFor({ state: 'hidden' }); // removing focus from toast await this.rootPage.locator('.nc-modal').click(); - await this.get().press('Escape'); + await this.get().locator('.nc-expanded-form-header').locator('.nc-expand-form-close-btn').click(); await this.get().waitFor({ state: 'hidden' }); } diff --git a/tests/playwright/pages/Dashboard/Grid/index.ts b/tests/playwright/pages/Dashboard/Grid/index.ts index c1998fdf3c..694af15598 100644 --- a/tests/playwright/pages/Dashboard/Grid/index.ts +++ b/tests/playwright/pages/Dashboard/Grid/index.ts @@ -441,6 +441,14 @@ export class GridPage extends BasePage { return this.get().locator('.cell.active').count(); } + async getActiveCell() { + return this.get().locator('.cell.active'); + } + + async verifySelectedCellCount({ count }: { count: number }) { + await expect(this.get().locator('.cell.active')).toHaveCount(count); + } + async copyWithKeyboard() { // retry to avoid flakiness, until text is copied to clipboard // diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts index c47ae79cc5..dc67552c54 100644 --- a/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts +++ b/tests/playwright/pages/Dashboard/common/Toolbar/Groupby.ts @@ -49,8 +49,12 @@ export class ToolbarGroupByPage extends BasePage { .locator(`div[label="${title}"]`) .last() .click(); + + //kludge: wait for rendering to stabilize + await this.rootPage.waitForTimeout(1000); + await this.rootPage.locator('.nc-sort-dir-select').nth(index).waitFor({ state: 'visible' }); - await this.rootPage.locator('.nc-sort-dir-select').nth(index).click(); + await this.rootPage.locator('.nc-sort-dir-select').nth(index).click({ force: true }); await this.rootPage .locator('.nc-dropdown-sort-dir') .last() @@ -139,7 +143,12 @@ export class ToolbarGroupByPage extends BasePage { async remove({ index }: { index: number }) { // open group-by menu await this.toolbar.clickGroupBy(); - await this.rootPage.locator('.nc-group-by-item-remove-btn').nth(index).click(); + + await this.waitForResponse({ + uiAction: () => this.rootPage.locator('.nc-group-by-item-remove-btn').nth(index).click(), + requestUrlPathToMatch: '/api/v1/db/data/noco', + httpMethodsToMatch: ['GET'], + }); // close group-by menu await this.toolbar.clickGroupBy(); diff --git a/tests/playwright/pages/SharedForm/index.ts b/tests/playwright/pages/SharedForm/index.ts index fe98197e5e..fc3fe7115e 100644 --- a/tests/playwright/pages/SharedForm/index.ts +++ b/tests/playwright/pages/SharedForm/index.ts @@ -25,7 +25,7 @@ export class SharedFormPage extends BasePage { } async verifySuccessMessage() { - await this.rootPage.locator('.nc-shared-form-success-msg').waitFor({ state: 'visible' }); + await this.rootPage.locator('.nc-shared-form-success-msg').waitFor({ state: 'visible', timeout: 10000 }); await expect( this.get().locator('.ant-alert-success', { hasText: 'Successfully submitted form data', diff --git a/tests/playwright/tests/db/columns/columnMultiSelect.spec.ts b/tests/playwright/tests/db/columns/columnMultiSelect.spec.ts index 0e63cb3c6a..e4fc5ba98e 100644 --- a/tests/playwright/tests/db/columns/columnMultiSelect.spec.ts +++ b/tests/playwright/tests/db/columns/columnMultiSelect.spec.ts @@ -3,6 +3,8 @@ import { DashboardPage } from '../../../pages/Dashboard'; import { GridPage } from '../../../pages/Dashboard/Grid'; import setup, { unsetup } from '../../../setup'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar'; +import { Api } from 'nocodb-sdk'; +let api: Api; test.describe('Multi select', () => { let dashboard: DashboardPage, grid: GridPage; @@ -206,6 +208,13 @@ test.describe('Multi select - filters', () => { toolbar = dashboard.grid.toolbar; grid = dashboard.grid; + api = new Api({ + baseURL: `http://localhost:8080/`, + headers: { + 'xc-auth': context.token, + }, + }); + await dashboard.treeView.createTable({ title: 'sheet1', baseTitle: context.base.title }); await grid.column.create({ title: 'MultiSelect', type: 'MultiSelect' }); @@ -213,12 +222,26 @@ test.describe('Multi select - filters', () => { columnTitle: 'MultiSelect', options: ['foo', 'bar', 'baz'], }); - await grid.addNewRow({ index: 0, value: '1' }); - await grid.addNewRow({ index: 1, value: '2' }); - await grid.addNewRow({ index: 2, value: '3' }); - await grid.addNewRow({ index: 3, value: '4' }); - await grid.addNewRow({ index: 4, value: '5' }); - await grid.addNewRow({ index: 5, value: '6' }); + + try { + const tables = await api.dbTable.list(context.base.id); + const rowAttributes = []; + for (let i = 0; i < 6; i++) { + const row = { + Id: i + 1, + Title: `${i + 1}`, + }; + rowAttributes.push(row); + } + + const tableId = tables.list.find((table: any) => table.table_name === 'sheet1').id; + await api.dbTableRow.bulkCreate('noco', context.base.id, tableId, rowAttributes); + } catch (e) { + console.error(e); + } + + // page reload + await page.reload(); await grid.cell.selectOption.select({ index: 1, columnHeader: 'MultiSelect', option: 'foo', multiSelect: true }); await grid.cell.selectOption.select({ index: 2, columnHeader: 'MultiSelect', option: 'bar', multiSelect: true }); diff --git a/tests/playwright/tests/db/features/command.spec.ts b/tests/playwright/tests/db/features/command.spec.ts index 6bbefb04e1..62adbd14b7 100644 --- a/tests/playwright/tests/db/features/command.spec.ts +++ b/tests/playwright/tests/db/features/command.spec.ts @@ -46,6 +46,8 @@ test.describe('Command Shortcuts', () => { await dashboard.cmdK.searchText('CustomerList'); + await page.waitForTimeout(1000); + await dashboard.get().locator('.nc-active-view-title').waitFor({ state: 'visible' }); await expect(dashboard.get().locator('.nc-active-view-title')).toContainText('Default View'); await dashboard.signOut(); diff --git a/tests/playwright/tests/db/general/cellSelection.spec.ts b/tests/playwright/tests/db/general/cellSelection.spec.ts index f16cac8144..aeebae1ac2 100644 --- a/tests/playwright/tests/db/general/cellSelection.spec.ts +++ b/tests/playwright/tests/db/general/cellSelection.spec.ts @@ -77,9 +77,9 @@ test.describe('Verify cell selection', () => { start: { index: 0, columnHeader: 'Country' }, end: { index: 2, columnHeader: 'Cities' }, }); - expect(await grid.selectedCount()).toBe(9); + await grid.verifySelectedCellCount({ count: 9 }); await grid.cell.get({ index: 0, columnHeader: 'Country' }).click(); - expect(await grid.selectedCount()).toBe(1); + await grid.verifySelectedCellCount({ count: 1 }); expect(await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Country' })); await dashboard.closeAllTabs(); @@ -89,9 +89,9 @@ test.describe('Verify cell selection', () => { start: { index: 0, columnHeader: 'Country' }, end: { index: 2, columnHeader: 'Cities' }, }); - expect(await grid.selectedCount()).toBe(9); + await grid.verifySelectedCellCount({ count: 9 }); await grid.cell.get({ index: 5, columnHeader: 'Country' }).click(); - expect(await grid.selectedCount()).toBe(1); + await grid.verifySelectedCellCount({ count: 1 }); expect(await grid.cell.verifyCellActiveSelected({ index: 5, columnHeader: 'Country' })); await dashboard.closeAllTabs(); @@ -102,9 +102,9 @@ test.describe('Verify cell selection', () => { start: { index: 2, columnHeader: 'Cities' }, end: { index: 0, columnHeader: 'Country' }, }); - expect(await grid.selectedCount()).toBe(12); + await grid.verifySelectedCellCount({ count: 12 }); await grid.cell.get({ index: 1, columnHeader: 'Country' }).click(); - expect(await grid.selectedCount()).toBe(1); + await grid.verifySelectedCellCount({ count: 1 }); expect(await grid.cell.verifyCellActiveSelected({ index: 1, columnHeader: 'Country' })); await dashboard.grid.toolbar.fields.toggleShowSystemFields(); await dashboard.closeAllTabs(); @@ -115,8 +115,10 @@ test.describe('Verify cell selection', () => { start: { index: 0, columnHeader: 'Country' }, end: { index: 2, columnHeader: 'Cities' }, }); + await grid.verifySelectedCellCount({ count: 9 }); + await grid.rootPage.waitForTimeout(100); await page.keyboard.press('ArrowRight'); - expect(await grid.selectedCount()).toBe(1); + await grid.verifySelectedCellCount({ count: 1 }); expect(await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'LastUpdate' })); await dashboard.closeAllTabs(); }); From 8317cae86ea99217669fb6ebcbcfa20b038dbbbc Mon Sep 17 00:00:00 2001 From: navi Date: Sat, 27 Apr 2024 08:38:46 +0100 Subject: [PATCH 05/46] New translations en.json (Turkish) (#8352) --- packages/nc-gui/lang/tr.json | 598 +++++++++++++++++------------------ 1 file changed, 299 insertions(+), 299 deletions(-) diff --git a/packages/nc-gui/lang/tr.json b/packages/nc-gui/lang/tr.json index 1c8466351d..a2e6d39171 100644 --- a/packages/nc-gui/lang/tr.json +++ b/packages/nc-gui/lang/tr.json @@ -75,39 +75,39 @@ "matterMost": "Mattermost", "twilio": "Twilio", "whatsappTwilio": "WhatsApp Twilio", - "quote": "Quote", + "quote": "Alıntı", "submit": "Gönder", "create": "Oluştur", - "createEntity": "Create {entity}", - "creating": "Creating", - "creatingEntity": "Creating {entity}", - "details": "Details", - "skip": "Skip", - "code": "Code", + "createEntity": "Oluştur {entity}", + "creating": "Oluşturuluyor", + "creatingEntity": "Oluşturuluyor {entity}", + "details": "Ayrıntılar", + "skip": "Atla", + "code": "Kod", "duplicate": "Yinelenen", - "duplicating": "Duplicating", - "activate": "Activate", - "action": "Action", + "duplicating": "Kopyalanıyor", + "activate": "Etkinleştir", + "action": "Eylem", "insert": "Ekle", "delete": "Sil", - "deleteEntity": "Delete {entity}", - "bulkInsert": "Bulk Insert", - "bulkDelete": "Bulk Delete", - "bulkUpdate": "Bulk Update", - "deleting": "Deleting", + "deleteEntity": "Sil {entity}", + "bulkInsert": "Toplu Ekle", + "bulkDelete": "Toplu Sil", + "bulkUpdate": "Toplu Güncelle", + "deleting": "Siliniyor", "update": "Güncelle", "rename": "Yeniden Adlandır", "reload": "Yenile", "reset": "Sıfırla", "install": "Yükle", "show": "Göster", - "access": "Access", - "visibility": "Visibility", + "access": "Erişim", + "visibility": "Görünürlük", "hide": "Gizle", - "deprecated": "Deprecated", + "deprecated": "Kullanım dışı", "showAll": "Hepsini Göster", "hideAll": "Hepsini Gizle", - "notFound": "Not found", + "notFound": "Bulunamadı", "showMore": "Daha fazla göster", "showOptions": "Seçenekleri göster", "hideOptions": "Seçenekleri gizle", @@ -127,8 +127,8 @@ "upload": "Yükle", "download": "İndir", "default": "Varsayılan", - "base": "Source", - "datasource": "Data Source", + "base": "Kaynak", + "datasource": "Veri Kaynağı", "more": "Fazla", "less": "Az", "event": "Olay", @@ -136,7 +136,7 @@ "after": "Sonra", "before": "Önce", "search": "Ara", - "searchIn": "Search In", + "searchIn": "Ara", "notification": "Bildirim", "reference": "Referans", "function": "Fonksiyon", @@ -157,32 +157,32 @@ "groupingField": "Gruplama Alanı", "insertAfter": "Sonra Ekle", "insertBefore": "Önce Ekle", - "insertAbove": "Insert above", - "insertBelow": "Insert below", + "insertAbove": "Yukarıya ekle", + "insertBelow": "Aşağı ekle", "hideField": "Alanı Gizle", "sortAsc": "Artan Sırala", "sortDesc": "Azalan Sıralama", - "move": "Move", + "move": "Taşı", "geoDataField": "GeoData Alanı", - "type": "Type", - "name": "Name", - "changes": "Changes", - "new": "New", - "old": "Old", - "data": "Data", - "source": "Source", - "destination": "Destination", - "active": "Active", - "inactive": "Inactive", - "linked": "linked", - "finish": "Finish", + "type": "Tür", + "name": "Ad", + "changes": "Değişiklikler", + "new": "Yeni", + "old": "Eski", + "data": "Veri", + "source": "Kaynak", + "destination": "Hedef", + "active": "Etkin", + "inactive": "İnaktif", + "linked": "bağlantılı", + "finish": "Bitir", "min": "Min", "max": "Max", - "avg": "Avg", - "sum": "Sum", - "count": "Count", - "countDistinct": "Count Distinct", - "sumDistinct": "Sum Distinct", + "avg": "Ort", + "sum": "Top", + "count": "Say", + "countDistinct": "Tekrarsız Say", + "sumDistinct": "Tekrarsız Toplam", "avgDistinct": "Farklı Değerlerin Ort", "join": "Bağla", "options": "Seçenekler", @@ -329,50 +329,50 @@ "rowData": "Kayıt verisi", "creator": "Yaratıcı", "qrCode": "QR Kod", - "termsOfService": "Terms of Service", - "updateSelectedRows": "Update Selected Records", - "noFiltersAdded": "No filters added", - "editCards": "Edit Cards", - "noFieldsFound": "No fields found", - "displayValue": "Display Value", - "expand": "Expand", - "hideAll": "Hide all", - "hideSystemFields": "Hide system fields", - "removeFile": "Remove File", - "hasMany": "Has Many", - "manyToMany": "Many to Many", - "oneToOne": "One to One", - "virtualRelation": "Virtual Relation", - "linkMore": "Link More", - "linkMoreRecords": "Link more records", - "linkRecords": "Link Records", - "downloadFile": "Download File", - "renameTable": "Rename Table", - "renamingTable": "Renaming Table", - "renamingWs": "Renaming Workspace", - "renameWs": "Rename Workspace", - "deleteWs": "Delete Workspace", - "deletingWs": "Deleting Workspace", - "copyAuthToken": "Copy Auth Token", - "copiedAuthToken": "Copied Auth Token", - "copyInviteToken": "Copy Invite Token", - "showSidebar": "Show Sidebar", - "hideSidebar": "Hide Sidebar", - "creatingTable": "Creating Table", + "termsOfService": "Hizmet Koşulları", + "updateSelectedRows": "Seçili Kayıtları Güncelle", + "noFiltersAdded": "Filtre yok", + "editCards": "Kartları Düzenle", + "noFieldsFound": "Hiç sütun bulunamadı", + "displayValue": "Gösterge Değeri", + "expand": "Genişlet", + "hideAll": "Tümünü gizle", + "hideSystemFields": "Sistem alanlarını gizle", + "removeFile": "Dosyayı Kaldır", + "hasMany": "Birden Çoğa", + "manyToMany": "Çoktan Çoğa", + "oneToOne": "Bire Bir", + "virtualRelation": "Sanal İlişki", + "linkMore": "İlişkilendir", + "linkMoreRecords": "Daha fazla kayıt ilişkilendir", + "linkRecords": "Kayıt İlişkilendir", + "downloadFile": "Dosyayı İndir", + "renameTable": "Tabloyu Yeniden Adlandır", + "renamingTable": "Tablo Yeniden Adlandırılıyor", + "renamingWs": "Çalışma Alanı Yeniden Adlandırılıyor", + "renameWs": "Çalışma Alanını Yeniden Adlandır", + "deleteWs": "Çalışma Alanını Sil", + "deletingWs": "Çalışma Alanı Yeniden Adlandırılıyor", + "copyAuthToken": "Yetki Token'ını Kopyala", + "copiedAuthToken": "Yetki Token'ı Kopyalandı", + "copyInviteToken": "Davet Token'ını Kopyala", + "showSidebar": "Kenar Çubuğunu Göster", + "hideSidebar": "Kenar Çubuğunu Gizle", + "creatingTable": "Tablo Oluşturuluyor", "erdView": "ERD Görünümü", - "newBase": "New Data Source", + "newBase": "Yeni Veri Kaynağı", "newProj": "Yeni proje", - "createBase": "Create Base", + "createBase": "Proje Oluştur", "myProject": "Benim projelerim", "formTitle": "Form başlığı", - "collaborative": "Collaborative", - "locked": "Locked", - "personal": "Personal", + "collaborative": "Paylaşılan", + "locked": "Kilitli", + "personal": "Kişisel", "appStore": "Uygulama mağazası", "teamAndAuth": "Takım & Yetki", "rolesUserMgmt": "Rol & Kullanıcı Yönetimi", "userMgmt": "Kullanıcı Yönetimi", - "apiTokens": "API Tokens", + "apiTokens": "API Tokenları", "apiTokenMgmt": "API Token Yönetimi", "rolesMgmt": "Rol Yönetimi", "projMeta": "Proje Metaveri", @@ -395,150 +395,150 @@ "generateToken": "Belirteç Oluştur", "APIsAndSupport": "API'ler & Destek", "helpCenter": "Yardım merkezi", - "noLabels": "No Labels", + "noLabels": "Etiket Yok", "swaggerDocumentation": "Swagger Dokümantasyonu", "quickImportFrom": "Hızlı İçe Aktarma", "quickImport": "Hızlı İçe Aktarma", - "quickImportAirtable": "Quick Import - Airtable", - "quickImportCSV": "Quick Import - CSV", - "quickImportExcel": "Quick Import - Excel", - "quickImportJSON": "Quick Import - JSON", - "jsonEditor": "JSON Editor", - "comingSoon": "Coming Soon", + "quickImportAirtable": "Hızlı İçe Aktarma - Airtable", + "quickImportCSV": "Hızlı İçe Aktarma - CSV", + "quickImportExcel": "Hızlı İçe Aktarma - Excel", + "quickImportJSON": "Hızlı İçe Aktarma - JSON", + "jsonEditor": "JSON Düzenleyici", + "comingSoon": "Çok Yakında", "advancedSettings": "Gelişmiş Ayarlar", "codeSnippet": "Kod Parçacığı", "keyboardShortcut": "Klavye Kısayolları", "generateRandomName": "Rastgele Ad Oluştur", "findRowByScanningCode": "QR veya Barkod okutarak satır bulun", - "tokenManagement": "Token Management", - "addNewToken": "Add new token", - "createNewToken": "Create new token", - "accountSettings": "Account Settings", - "resetPasswordMenu": "Reset Password", - "tokens": "Tokens", - "userManagement": "User Management", + "tokenManagement": "Token Yönetimi", + "addNewToken": "Yeni token ekle", + "createNewToken": "Yeni token oluştur", + "accountSettings": "Hesap Ayarları", + "resetPasswordMenu": "Parola Sıfırla", + "tokens": "Tokenler", + "userManagement": "Kullanıcı Yönetimi", "accountManagement": "Hesap yönetimi", - "licence": "Licence", - "allowAllMimeTypes": "Allow All Mime Types", - "defaultView": "Default View", - "relations": "Relations", - "switchLanguage": "Switch Language", - "renameFile": "Rename File", + "licence": "Lisans", + "allowAllMimeTypes": "Tüm Mime Türlerine İzin Ver", + "defaultView": "Varsayılan Görünüm", + "relations": "İlişkiler", + "switchLanguage": "Dil Değiştir", + "renameFile": "Dosyayı Yeniden Adlandır", "links": { - "noAction": "No Action", - "cascade": "Cascade", - "restrict": "Restrict", - "setNull": "Set NULL", - "setDefault": "Set Default" + "noAction": "Eylem Yok", + "cascade": "Kaskat", + "restrict": "Kısıtla", + "setNull": "NULL olarak ayarla", + "setDefault": "Varsayılan olarak ayarla" }, - "selectFieldsFromRightPannelToAddHere": "Select fields from right panel to add here", - "noOptionsFound": "No options found", - "surveyFormSubmitConfirmMsg": "Are you sure you want to submit this form?", + "selectFieldsFromRightPannelToAddHere": "Buraya eklemek için sağ panelden alanları seçin", + "noOptionsFound": "Seçenek bulunamadı", + "surveyFormSubmitConfirmMsg": "Bu formu göndermek istediğinizden emin misiniz?", "noResultsMatchedYourSearch": "Your search did not yield any matching results." }, "labels": { - "selectYear": "Select Year", - "save": "Save", - "cancel": "Cancel", - "metadataUrl": "Metadata URL", - "audience-entityId": "Audience/ Entity ID", - "redirectUrl": "Redirect URL", + "selectYear": "Yıl Seçin", + "save": "Kaydet", + "cancel": "İptal", + "metadataUrl": "Metaveri URL'si", + "audience-entityId": "Kitle/Kurum ID", + "redirectUrl": "Yönlendirme URL'si", "oidc": "OpenID Connect (OIDC)", "saml": "Security Assertion Markup Language (SAML)", - "newProvider": "New Provider", - "generalSettings": "General Settings", - "ssoSettings": "SSO Settings", + "newProvider": "Yeni Sağlayıcı", + "generalSettings": "Genel Ayarlar", + "ssoSettings": "SSO Ayarları", "organizeBy": "Organize by", - "previous": "Previous", - "nextMonth": "Next Month", - "previousMonth": "Previous Month", - "next": "Next", + "previous": "Önceki", + "nextMonth": "Sonraki Ay", + "previousMonth": "Önceki Ay", + "next": "Sonraki", "organiseBy": "Organise by", - "heading1": "Heading 1", - "heading2": "Heading 2", - "heading3": "Heading 3", - "bold": "Bold", - "italic": "Italic", - "underline": "Underline", - "strike": "Strike", - "taskList": "Task List", - "bulletList": "Bullet List", - "numberedList": "Numbered List", - "downloadData": "Download Data", - "blockQuote": "Block Quote", - "noToken": "No Token", - "tokenLimit": "Only one token per user is allowed", - "duplicateAttachment": "File with name {filename} already attached", - "tableIdColon": "TABLE ID: {tableId}", - "viewIdColon": "VIEW ID: {viewId}", - "toAddress": "To Address", - "subject": "Subject", - "body": "Body", - "commaSeparatedMobileNumber": "Comma separated Mobile #", - "headerName": "Header Name", - "icon": "Icon", + "heading1": "Başlık 1", + "heading2": "Başlık 2", + "heading3": "Başlık 3", + "bold": "Kalın", + "italic": "İtalik", + "underline": "Alt Çizgi", + "strike": "Üst Çizgi", + "taskList": "Görev Listesi", + "bulletList": "Noktalı Liste", + "numberedList": "Numaralı Liste", + "downloadData": "Verileri İndir", + "blockQuote": "Alıntı Bloğu", + "noToken": "Token Yok", + "tokenLimit": "Kullanıcı başına yalnızca bir token oluşturulabilir", + "duplicateAttachment": "{filename} adlı dosya zaten ekli", + "tableIdColon": "Tablo ID: {tableId}", + "viewIdColon": "Görünüm ID: {viewId}", + "toAddress": "Adres", + "subject": "Konu", + "body": "Sayfa Gövdesi", + "commaSeparatedMobileNumber": "Virgülle ayrılmış Cep #", + "headerName": "Başlık Adı", + "icon": "Simge", "max": "Max", - "enableRichText": "Enable Rich Text", + "enableRichText": "Zengin Metni Etkinleştir", "idColon": "Id:", - "copiedRecordURL": "Copied Record URL", - "copyRecordURL": "Copy Record URL", - "duplicateRecord": "Duplicate record", - "binaryEncodingFormat": "Binary encoding format", - "syntax": "Syntax", - "examples": "Examples", - "durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", - "addHeader": "Add Header", - "enterDefaultUrlOptional": "Enter default URL (Optional)", - "negative": "Negative", - "discard": "Discard", - "default": "Default", - "defaultNumberPercent": "Default Number (%)", - "durationFormat": "Duration Format", - "dateFormat": "Date Format", - "timeFormat": "Time Format", - "singularLabel": "Singular Label", - "pluralLabel": "Plural Label", - "selectDateField": "Select a date field", - "endDateField": "End date field", - "optional": "(Optional)", - "clickToMake": "Click to make", - "visibleForRole": "visible for role:", + "copiedRecordURL": "Kayıt URL'si Kopyalandı", + "copyRecordURL": "Kayıt URL'sini Kopyala", + "duplicateRecord": "Kaydı Çoğalt", + "binaryEncodingFormat": "Binary kodlama biçimi", + "syntax": "Sözdizimi", + "examples": "Örnekler", + "durationInfo": "Dakika ve saniye cinsinden bir zaman süresi (örn. 1:23).", + "addHeader": "Üst bilgi ekle", + "enterDefaultUrlOptional": "Varsayılan URL (İsteğe bağlı)", + "negative": "Negatif", + "discard": "Vazgeç", + "default": "Varsayılan", + "defaultNumberPercent": "Varsayılan Sayı (%)", + "durationFormat": "Süre Biçimi", + "dateFormat": "Tarih Biçimi", + "timeFormat": "Zaman Biçimi", + "singularLabel": "Tekil Etiket", + "pluralLabel": "Çoğul Etiket", + "selectDateField": "Bir tarih sütunu seçin", + "endDateField": "Bitiş tarihi sütunu", + "optional": "(Opsiyonel)", + "clickToMake": "Yapmak için tıklayın", + "visibleForRole": "rol için görünür:", "inUI": "in UI Dashboard", - "projectSettings": "Base Settings", - "clickToHide": "Click to hide", - "clickToDownload": "Click to download", - "forRole": "for role", - "clickToCopyTableID": "Click to copy Table ID", - "clickToCopyViewID": "Click to copy View ID", - "viewMode": "View Mode", - "searchUsers": "Search Users", - "superAdmin": "Super Admin", - "allTables": "All Tables", - "members": "Members", - "dataSources": "Data Sources", - "connectDataSource": "Connect a Data Source", - "searchProjects": "Search Bases", + "projectSettings": "Proje Ayarları", + "clickToHide": "Gizlemek için tıklayın", + "clickToDownload": "İndirmek için tıklayın", + "forRole": "rol için", + "clickToCopyTableID": "Tablo ID'sini kopyalamak için tıklayın", + "clickToCopyViewID": "Görünüm ID'sini kopyalamak için tıklayın", + "viewMode": "Görünüm Modu", + "searchUsers": "Kullanıcıları Ara", + "superAdmin": "Süper Admin", + "allTables": "Tüm Tablolar", + "members": "Üyeler", + "dataSources": "Veri Kaynakları", + "connectDataSource": "Veri Kaynağına Bağlan", + "searchProjects": "Proje Ara", "createdBy": "Tarafından Oluşturuldu", - "viewingAttachmentsOf": "Viewing Attachments of", - "readOnly": "Readonly", - "dropHere": "Drop here", - "createdOn": "Created On", + "viewingAttachmentsOf": "Ekleri Görüntüleniyor", + "readOnly": "Salt okunur", + "dropHere": "Buraya bırak", + "createdOn": "Oluşturuldu", "notifyVia": "Aracılığıyla Bildir", "projName": "Proje adı", - "profile": "Profile", - "accountDetails": "Account Details", - "controlAppearance": "Control your Appearance.", - "accountEmailID": "Account Email ID", - "backToWorkspace": "Back to Workspace", - "untitledToken": "Untitled token", + "profile": "Profil", + "accountDetails": "Hesap Ayrıntıları", + "controlAppearance": "Görünüşünüzü kontrol edin.", + "accountEmailID": "Hesap E-postası", + "backToWorkspace": "Çalışma Alanına Geri Dön", + "untitledToken": "İsimsiz token", "tableName": "Tablo adı", "dashboardName": "Dashboard name", - "createView": "Create a View", - "creatingView": "Creating View", - "duplicateView": "Duplicate View", - "duplicateGridView": "Duplicate Grid View", - "createGridView": "Create Grid View", - "duplicateGalleryView": "Duplicate Gallery View", + "createView": "Bir görünüm oluştur", + "creatingView": "Görünüm Oluşturuluyor", + "duplicateView": "Görünümü Kopyala", + "duplicateGridView": "Tablo Görünümü Kopyala", + "createGridView": "Tablo görünümü oluştur", + "duplicateGalleryView": "Galeri Görünümü Kopyala", "createGalleryView": "Create Gallery View", "duplicateFormView": "Duplicate Form View", "createFormView": "Create Form View", @@ -693,73 +693,73 @@ "multiField": { "newField": "New field", "saveChanges": "Save changes", - "updatedField": "Updated field", - "deletedField": "Deleted field", - "incompleteConfiguration": "Incomplete configuration", - "selectField": "Select a field", + "updatedField": "Güncellenen sütun", + "deletedField": "Silinen sütun", + "incompleteConfiguration": "Eksik yapılandırma", + "selectField": "Bir sütun seç", "selectFieldLabel": "Begin by selecting a field to customise its properties and structure." }, - "appearanceSettings": "Appearance Settings", - "backgroundColor": "Background Color", - "hideNocodbBranding": "Hide NocoDB Branding", + "appearanceSettings": "Görüntü Ayarları", + "backgroundColor": "Arkaplan Rengi", + "hideNocodbBranding": "NocoDB Markasını Gizle", "showOnConditions": "Show on conditions", - "showFieldOnConditionsMet": "Shows field only when conditions are met", - "limitOptions": "Limit options", - "limitOptionsSubtext": "Limit options visible to users by selecting available options", - "clearSelection": "Clear selection" + "showFieldOnConditionsMet": "Sütunu yalnızca koşullar karşılandığında göster", + "limitOptions": "Seçenekleri sınırlayın", + "limitOptionsSubtext": "Kullanıcıya görüntülenen seçenekleri mevcut seçeneklerden seçerek sınırlayın", + "clearSelection": "Seçimi temizle" }, "activity": { - "addMembers": "Add Members", - "enterEmail": "Enter email addresses", - "inviteToBase": "Invite to Base", - "addMember": "Add Member to Base", - "noRange": "Calendar view requires a date range", - "goToToday": "Go to Today", - "toggleSidebar": "Toggle Sidebar", - "addEndDate": "Add end date", - "withEndDate": "with end date", - "calendar": "Calendar", - "viewSettings": "View settings", + "addMembers": "Üye Ekle", + "enterEmail": "E-posta adresi girin", + "inviteToBase": "Projeye Davet Et", + "addMember": "Projeye Üye Ekle", + "noRange": "Takvim görünümü bir tarih aralığı gerektirir", + "goToToday": "Bugün", + "toggleSidebar": "Kenar Çubuğunu Aç/Kapat", + "addEndDate": "Bitiş tarihi ekleyin", + "withEndDate": "bitiş tarihi ile", + "calendar": "Takvim", + "viewSettings": "Görünüm ayarları", "googleOAuth": "Google OAuth", - "registerOIDC": "Register OIDC Identity Provider", - "registerSAML": "Register SAML Identity Provider", - "openInANewTab": "Open in a new tab", - "copyIFrameCode": "Copy IFrame code", - "onCondition": "On Condition", - "bulkDownload": "Bulk Download", - "attachFile": "Attach File", - "viewAttachment": "View Attachments", - "attachmentDrop": "Click or drop a file into cell", - "addFiles": "Add File(s)", - "hideInUI": "Hide in UI", - "addBase": "Add Base", - "addParameter": "Add Parameter", - "submitAnotherForm": "Submit Another Form", - "dragAndDropFieldsHereToAdd": "Drag and drop fields here to add", - "editSource": "Edit Data Source", - "enterText": "Enter text", - "okEditBase": "Ok & Edit Base", - "showInUI": "Show in UI", - "outOfSync": "Out of sync", - "newSource": "New Data Source", - "newWebhook": "New Webhook", - "enablePublicAccess": "Enable Public Access", - "doYouWantToSaveTheChanges": "Do you want to save the changes ?", - "editingAccess": "Editing access", - "enabledPublicViewing": "Enable Public Viewing", - "restrictAccessWithPassword": "Restrict access with password", - "manageProjectAccess": "Manage Base Access", - "allowDownload": "Allow Download", - "surveyMode": "Survey Mode", - "rtlOrientation": "RTL Orientation", - "useTheme": "Use Theme", - "copyLink": "Copy Link", - "copiedLink": "Link Copied", - "copyInviteLink": "Copy invite link", - "copiedInviteLink": "Copied invite link", + "registerOIDC": "OIDC Kimlik Sağlayıcısı Ekle", + "registerSAML": "SAML Kimlik Sağlayıcısı Ekle", + "openInANewTab": "Yeni sekmede aç", + "copyIFrameCode": "IFrame kodunu kopyala", + "onCondition": "Koşullu", + "bulkDownload": "Toplu İndirme", + "attachFile": "Dosya Ekle", + "viewAttachment": "Ekleri Görüntüle", + "attachmentDrop": "Tıklayın ya da hücreye bir dosya bırakın", + "addFiles": "Dosya(lar) Ekle", + "hideInUI": "Arayüzde Gizle", + "addBase": "Proje Ekle", + "addParameter": "Parametre Ekle", + "submitAnotherForm": "Başka Bir Form Gönder", + "dragAndDropFieldsHereToAdd": "Eklemek için sütunları buraya sürükleyip bırakın", + "editSource": "Veri Kaynağını Düzenle", + "enterText": "Metin girin", + "okEditBase": "Tamam & Projeyi Kaydet", + "showInUI": "Arayüzde Göster", + "outOfSync": "Senkronizasyon dışı", + "newSource": "Yeni Veri Kaynağı", + "newWebhook": "Yeni Webhook", + "enablePublicAccess": "Dışa Erişimi Etkinleştir", + "doYouWantToSaveTheChanges": "Değişiklikleri kaydetmek istiyor musunuz?", + "editingAccess": "Erişim düzenleniyor", + "enabledPublicViewing": "Dışa Görüntülemeyi Etkinleştir", + "restrictAccessWithPassword": "Erişimi parola ile sınırlandırın", + "manageProjectAccess": "Proje Erişimini Yönet", + "allowDownload": "İndirmeye İzin Verin", + "surveyMode": "Anket Modu", + "rtlOrientation": "RTL Oryantasyonu", + "useTheme": "Tema Kullanın", + "copyLink": "Bağlantıyı Kopyala", + "copiedLink": "Bağlantı Kopyalandı", + "copyInviteLink": "Davet bağlantısını kopyala", + "copiedInviteLink": "Davet bağlantısı kopyalandı", "copyUrl": "Linki kopyala", - "moreColors": "More Colors", - "moveProject": "Move Base", + "moreColors": "Daha Fazla Renk", + "moveProject": "Projeyi Taşı", "createProject": "Proje Oluştur", "importProject": "Projeyi içe aktar", "searchProject": "Proje ara", @@ -770,7 +770,7 @@ "deleteProject": "Projeyi Sil", "refreshProject": "Projeleri Yenile", "saveProject": "Projeyi Kaydet", - "saveAndQuit": "Save & Quit", + "saveAndQuit": "Kaydet ve Çık", "deleteKanbanStack": "Yığını silelim mi?", "createProjectExtended": { "extDB": "Dışarıdan bir veritabanına
bağlanarak oluştur", @@ -786,7 +786,7 @@ "translate": "Çeviriye yardım edin", "account": { "authToken": "Yetki Token'ını Kopyala", - "authTokenCopied": "Copied Auth Token", + "authTokenCopied": "Yetki Token'ı Kopyalandı", "swagger": "Swagger: REST APIleri", "projInfo": "Proje Bilgisini Kopyala", "themes": "Temalar" @@ -796,10 +796,10 @@ "filter": "Filtrele", "addFilter": "Filtre Ekle", "share": "Paylaş", - "groupBy": "Group By", - "addSubGroup": "Add subgroup", + "groupBy": "Grupla", + "addSubGroup": "Alt grup ekle", "shareBase": { - "label": "Share Base", + "label": "Projeyi Paylaş", "disable": "Paylaşılan bağlantıyı devre dışı bırak", "enable": "Bağlantıya sahip olan herkes", "link": "Paylaşılan bağlantı" @@ -809,8 +809,8 @@ "inviteTeam": "Takıma davet et", "inviteUser": "Kullanıcı Davet Et", "inviteToken": "Davet kodu", - "linkedRecords": "Linked Records", - "addNewLink": "Add New Link", + "linkedRecords": "İlişkili Kayıtlar", + "addNewLink": "Yeni İlişki Ekle", "newUser": "Yeni kullanıcı", "editUser": "Kullanıcıyı düzenle", "deleteUser": "Kullanıcıyı projeden kaldır", @@ -826,10 +826,10 @@ "copyApiURL": "API linkini kopyala", "createTable": "Yeni tablo oluştur", "createDashboard": "Create Dashboard", - "createWorkspace": "Create Workspace", + "createWorkspace": "Çalışma Alanı Oluştur", "refreshTable": "Tabloları Yenile", "renameTable": "Tabloyu Yeniden Adlandır", - "renameLayout": "Layout Rename", + "renameLayout": "Düzeni Yeniden Adlandır", "deleteTable": "Tabloyu Sil", "addField": "Tabloya yeni alan ekle", "setDisplay": "Görünüm değeri olarak ayarla", @@ -840,9 +840,9 @@ "insertRow": "Yeni Satır Ekle", "duplicateRow": "Satırı Çoğalt", "deleteRow": "Satırı Sil", - "deleteRows": "Delete records", - "predictColumns": "Predict Fields", - "predictFormulas": "Predict Formulas", + "deleteRows": "Kayıtları sil", + "predictColumns": "Sütunları Tahmin Et", + "predictFormulas": "Formülleri Tahmin Et", "deleteSelectedRow": "Seçilen Satırları Sil", "importExcel": "Excel içe aktar", "importCSV": "CSV içe aktar", @@ -863,7 +863,7 @@ "ListView": "Görünüm Listesi", "copyView": "Görünümü kopyala", "renameView": "Görünümü yeniden adlandır", - "uploadData": "Upload Data", + "uploadData": "Veri Yükle", "deleteView": "Görünümü sil", "createGrid": "Tablo görünümü oluştur", "createGallery": "Galeri görünümü oluştur", @@ -896,23 +896,23 @@ "addFilterGroup": "Filtre Grubu Ekle", "linkRecord": "Bağlantı kaydı", "addNewRecord": "Yeni kayıt ekle", - "newRecord": "New record", - "tableNameCreateNewRecord": "{tableName}: Create new record", - "gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' got saved & linked successfully", - "recordCreatedLinked": "Record Created & Linked", + "newRecord": "Yeni kayıt", + "tableNameCreateNewRecord": "{tableName}: Yeni kayıt oluştur", + "gotSavedLinkedSuccessfully": "{tableName} '{recordTitle}' kaydedildi ve başarıyla ilişkilendirildi", + "recordCreatedLinked": "Kayıt Oluşturuldu ve İlişkilendirildi", "useConnectionUrl": "Bağlantı URL'sini Kullan", "toggleCommentsDraw": "Yorum çizimini değiştir", "expandRecord": "Kaydı Genişlet", "deleteRecord": "Kayıt Silme", - "fullWidth": "Full width", - "exitFullWidth": "Exit full width", - "markAllAsRead": "Mark all as read", + "fullWidth": "Tam genişlik", + "exitFullWidth": "Tam genişlikten çık", + "markAllAsRead": "Tümünü okundu olarak işaretle", "column": { - "delete": "Delete Field", - "addNumber": "Add Number Field", - "addSingleLineText": "Add SingleLineText Field", - "addLongText": "Add LongText Field", - "addOther": "Add Other Field" + "delete": "Sütunu Sil", + "addNumber": "Sayı Sütunu Ekle", + "addSingleLineText": "Yazı Sütunu Ekle", + "addLongText": "Metin Sütunu Ekle", + "addOther": "Diğer Sütun Ekle" }, "erd": { "showColumns": "Sütunları Göster", @@ -935,10 +935,10 @@ "openInOpenStreetMap": "OSM" }, "toggleMobileMode": "Mobil Modu Aç / Kapat", - "startCommenting": "Start commenting!", - "clearForm": "Clear Form", - "addFieldFromFormView": "Add Field", - "selectAllFields": "Select all fields", + "startCommenting": "Yorum yapmaya başlayın!", + "clearForm": "Formu Temizle", + "addFieldFromFormView": "Sütun Ekle", + "selectAllFields": "Tüm sütunları seç", "preFilledFields": { "title": "Enable Pre-fill", "default": "Default", @@ -984,19 +984,19 @@ }, "placeholder": { "selectSlackChannels": "Select Slack channels", - "selectTeamsChannels": "Select Microsoft Teams channels", - "selectDiscordChannels": "Select Discord channels", - "selectMattermostChannels": "Select Mattermost channels", - "webhookTitle": "Webhook Title", - "barcodeColumn": "Select a field for the Barcode value", - "notFoundContent": "No valid field Type can be found.", - "selectBarcodeFormat": "Select a Barcode format", + "selectTeamsChannels": "Microsoft Teams kanallarını seçin", + "selectDiscordChannels": "Discord kanallarını seçin", + "selectMattermostChannels": "Mattermost kanallarını seçin", + "webhookTitle": "Webhook Başlığı", + "barcodeColumn": "Barkod değeri için bir sütun seçin", + "notFoundContent": "Geçerli sütun türü bulunamadı.", + "selectBarcodeFormat": "Bir Barkod biçimi seçin", "projName": "Proje Adını Girin", - "selectGroupField": "Select a Grouping Field", - "selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.", - "selectGeoField": "Select a GeoData Field", - "notSelected": "-not selected-", - "selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.", + "selectGroupField": "Gruplama Sütunu Seçin", + "selectGroupFieldNotFound": "Tekil seçim sütunu bulunamadı. Lütfen önce bir tane oluşturun.", + "selectGeoField": "GeoData Sütunu Seçin", + "notSelected": "-seçilmedi-", + "selectGeoFieldNotFound": "GeoData Sütunu bulunamadı. Lütfen önce bir tane oluşturun.", "password": { "enter": "Şifreyi Girin", "current": "Mevcut Şifre", @@ -1004,8 +1004,8 @@ "save": "Şifreyi Kaydet", "confirm": "Yeni şifreyi onayla" }, - "selectAColumnForTheQRCodeValue": "Select a field for the QR code value", - "allowNegativeNumbers": "Allow negative numbers", + "selectAColumnForTheQRCodeValue": "QR Kod değeri için bir sütun seçin", + "allowNegativeNumbers": "Negatif sayılara izin verin", "searchProjectTree": "Tabloları ara", "searchFields": "Sütunları ara", "searchColumn": "{search} sütununda ara", From 575d8445dda321c061b1fa37cbc361c9d6a9dd83 Mon Sep 17 00:00:00 2001 From: Rohit <45072928+rohittp0@users.noreply.github.com> Date: Sun, 28 Apr 2024 00:01:30 +0530 Subject: [PATCH 06/46] Apply suggestions from code review Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- docker-compose/setup-script/noco.sh | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docker-compose/setup-script/noco.sh b/docker-compose/setup-script/noco.sh index 9cbf69ebfe..61adcc8df3 100755 --- a/docker-compose/setup-script/noco.sh +++ b/docker-compose/setup-script/noco.sh @@ -140,7 +140,7 @@ show_menu() { clear check_if_docker_is_running echo "" - echo $MSG + echo "$MSG" echo -e "\t\t${BOLD}Service Management Menu${NC}" echo -e " ${GREEN}1. Start Service" echo -e " ${ORANGE}2. Stop Service" @@ -173,13 +173,13 @@ show_logs_sub_menu() { echo "A. All" echo "0. Back to Logs Menu" echo "Enter replica number: " - read replica_choice + read -r replica_choice if [[ "$replica_choice" =~ ^[0-9]+$ ]] && [ "$replica_choice" -gt 0 ] && [ "$replica_choice" -le "$2" ]; then container_id=$($DOCKER_COMMAND compose ps | grep "$1-$replica_choice" | cut -d " " -f 1) $DOCKER_COMMAND logs -f "$container_id" elif [ "$replica_choice" == "A" ] || [ "$replica_choice" == "a" ]; then - $DOCKER_COMMAND compose logs -f $1 + $DOCKER_COMMAND compose logs -f "$1" elif [ "$replica_choice" == "0" ]; then show_logs else @@ -205,7 +205,7 @@ show_logs() { # For each service, count the number of running instances for service in "${services[@]}"; do # Count the number of lines that have the service name, which corresponds to the number of replicas - replicas=$($DOCKER_COMMAND compose ps $service | grep "$service" | wc -l) + replicas=$($DOCKER_COMMAND compose ps "$service" | grep -c "$service") service_replicas["$count"]=$replicas count=$((count + 1)) done @@ -220,7 +220,7 @@ show_logs() { echo "A. All" echo "0. Back to main menu" echo "Enter your choice: " - read log_choice + read -r log_choice echo if [[ "$log_choice" =~ ^[0-9]+$ ]] && [ "$log_choice" -gt 0 ] && [ "$log_choice" -lt "$count" ]; then @@ -268,14 +268,14 @@ scale_service() { current_scale=$($DOCKER_COMMAND compose ps -q nocodb | wc -l) echo -e "\nCurrent number of instances: $current_scale" echo "How many instances of NocoDB do you want to run (Maximum: ${num_cores}) ? (default: 1): " - scale_num=$(read_number_range 1 $num_cores) + scale_num=$(read_number_range 1 "$num_cores") - if [ $scale_num -eq $current_scale ]; then + if [ "$scale_num" -eq "$current_scale" ]; then echo "Number of instances is already set to $scale_num. Returning to main menu." return fi - $DOCKER_COMMAND compose up -d --scale nocodb=$scale_num + $DOCKER_COMMAND compose up -d --scale nocodb="$scale_num" } # Function for basic monitoring @@ -292,7 +292,7 @@ management_menu() { show_menu echo "Enter your choice: " - read choice + read -r choice case $choice in 1) start_service && MSG="NocoDB Started" ;; 2) stop_service && MSG="NocoDB Stopped" ;; @@ -344,7 +344,8 @@ if [ "$NOCO_FOUND" = true ]; then read -r REINSTALL if [ -f "$NOCO_HOME/.COMPOSE_PROJECT_NAME" ]; then - export COMPOSE_PROJECT_NAME=$(cat "$NOCO_HOME/.COMPOSE_PROJECT_NAME") + COMPOSE_PROJECT_NAME=$(cat "$NOCO_HOME/.COMPOSE_PROJECT_NAME") + export COMPOSE_PROJECT_NAME fi if [ "$REINSTALL" != "Y" ] && [ "$REINSTALL" != "y" ]; then @@ -355,7 +356,7 @@ if [ "$NOCO_FOUND" = true ]; then $DOCKER_COMMAND compose down unset COMPOSE_PROJECT_NAME - cd /tmp + cd /tmp || exit 1 rm -rf "$NOCO_HOME" mkdir -p "$NOCO_HOME" From dac781368b5baa25babd06c2ee054d016c5954d0 Mon Sep 17 00:00:00 2001 From: Mahesh Dhoran <97591552+maheshdhoran@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:51:46 +0000 Subject: [PATCH 07/46] fix: map PostgreSQL 'double precision' correctly as 'float' (closes #8337) --- packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts | 2 +- .../nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts index 07006bd231..ab4d2ea19b 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts @@ -1436,7 +1436,7 @@ export class PgUi { case 'daterange': return 'string'; case 'double precision': - return 'string'; + return 'float'; case 'event_trigger': case 'fdw_handler': diff --git a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts index 93efd3d4c3..78eea3da5e 100644 --- a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts +++ b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts @@ -114,7 +114,7 @@ class ModelXcMetaPg extends BaseModelXcMeta { str = 'date'; break; case 'double precision': - str = 'double'; + str = 'float'; break; case 'event_trigger': str = 'event_trigger'; @@ -430,13 +430,12 @@ class ModelXcMetaPg extends BaseModelXcMeta { return 'date'; case 'daterange': return 'string'; - case 'double precision': - return 'string'; case 'event_trigger': case 'fdw_handler': return dt; + case 'double precision': case 'float4': case 'float8': return 'float'; From df260a05c4a0bc6a4798c540cc31c742fd82883a Mon Sep 17 00:00:00 2001 From: Mahesh Dhoran <97591552+maheshdhoran@users.noreply.github.com> Date: Sun, 28 Apr 2024 02:02:21 +0000 Subject: [PATCH 08/46] requested changes --- packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts | 3 +-- packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts index ab4d2ea19b..95abb59a61 100644 --- a/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts +++ b/packages/nocodb-sdk/src/lib/sqlUi/PgUi.ts @@ -1435,13 +1435,12 @@ export class PgUi { return 'date'; case 'daterange': return 'string'; - case 'double precision': - return 'float'; case 'event_trigger': case 'fdw_handler': return 'string'; + case 'double precision': case 'float4': case 'float8': return 'float'; diff --git a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts index 78eea3da5e..1ddd306e20 100644 --- a/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts +++ b/packages/nocodb/src/db/sql-mgr/code/models/xc/ModelXcMetaPg.ts @@ -114,7 +114,7 @@ class ModelXcMetaPg extends BaseModelXcMeta { str = 'date'; break; case 'double precision': - str = 'float'; + str = 'double'; break; case 'event_trigger': str = 'event_trigger'; From 041924adde46ca9d1eb9dfa6665d0ee5d1b16f1c Mon Sep 17 00:00:00 2001 From: mertmit Date: Sun, 28 Apr 2024 02:41:50 +0000 Subject: [PATCH 09/46] fix: group by api calls --- .../nc-gui/components/shared-view/Grid.vue | 3 +- .../components/smartsheet/grid/GroupBy.vue | 37 +- .../components/smartsheet/grid/index.vue | 4 +- .../components/smartsheet/header/Menu.vue | 2 +- .../smartsheet/toolbar/CreateGroupBy.vue | 2 +- .../smartsheet/toolbar/GroupByMenu.vue | 2 +- .../nc-gui/components/tabs/Smartsheet.vue | 3 +- packages/nc-gui/composables/useViewGroupBy.ts | 988 +++++++++--------- 8 files changed, 529 insertions(+), 512 deletions(-) diff --git a/packages/nc-gui/components/shared-view/Grid.vue b/packages/nc-gui/components/shared-view/Grid.vue index 747226cf83..7e14057284 100644 --- a/packages/nc-gui/components/shared-view/Grid.vue +++ b/packages/nc-gui/components/shared-view/Grid.vue @@ -23,7 +23,7 @@ const { signedIn } = useGlobal() const { loadProject } = useBase() -const { isLocked } = useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) +const { isLocked, xWhere } = useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideKanbanViewStore(meta, sharedView) useProvideCalendarViewStore(meta, sharedView) @@ -41,6 +41,7 @@ provide(IsPublicInj, ref(true)) provide(IsLockedInj, isLocked) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) +useProvideViewGroupBy(sharedView, meta, xWhere, true) if (signedIn.value) { try { diff --git a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue index ac4105709c..3e1911a16a 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue @@ -37,6 +37,16 @@ const { isViewDataLoading, isPaginationLoading } = storeToRefs(useViewsStore()) const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook()) +const _loadGroupData = async (group: Group, force?: boolean, params?: any) => { + isViewDataLoading.value = true + isPaginationLoading.value = true + + await props.loadGroupData(group, force, params) + + isViewDataLoading.value = false + isPaginationLoading.value = false +} + const _depth = props.depth ?? 0 const wrapper = ref() @@ -72,7 +82,7 @@ const findAndLoadSubGroup = (key: any) => { if (grp.nested) { if (!grp.children?.length) props.loadGroups({}, grp) } else { - if (!grp.rows?.length || grp.count !== grp.rows?.length) props.loadGroupData(grp) + if (!grp.rows?.length || grp.count !== grp.rows?.length) _loadGroupData(grp) } } } @@ -84,36 +94,29 @@ const reloadViewDataHandler = (params: void | { shouldShowLoading?: boolean | un if (vGroup.value.nested) { props.loadGroups({ ...(params?.offset !== undefined ? { offset: params.offset } : {}) }, vGroup.value) } else { - props.loadGroupData(vGroup.value, true, { + _loadGroupData(vGroup.value, true, { ...(params?.offset !== undefined ? { offset: params.offset } : {}), }) } } +onMounted(async () => { + reloadViewDataHook?.on(reloadViewDataHandler) +}) + onBeforeUnmount(async () => { reloadViewDataHook?.off(reloadViewDataHandler) }) -reloadViewDataHook?.on(reloadViewDataHandler) - watch( [() => vGroup.value.key], async (n, o) => { if (n !== o) { - isViewDataLoading.value = true - isPaginationLoading.value = true - - if (vGroup.value.nested) { - await props.loadGroups({}, vGroup.value) - } else { - await props.loadGroupData(vGroup.value, true) + if (!vGroup.value.nested) { + await _loadGroupData(vGroup.value, true) } - - isViewDataLoading.value = false - isPaginationLoading.value = false } }, - { immediate: true }, ) if (vGroup.value.root === true) provide(ScrollParentInj, wrapper) @@ -328,7 +331,7 @@ const shouldRenderCell = (column) => v-if="!grp.nested && grp.rows" :group="grp" :load-groups="loadGroups" - :load-group-data="loadGroupData" + :load-group-data="_loadGroupData" :load-group-page="loadGroupPage" :group-wrapper-change-page="groupWrapperChangePage" :row-height="rowHeight" @@ -345,7 +348,7 @@ const shouldRenderCell = (column) => v-else :group="grp" :load-groups="loadGroups" - :load-group-data="loadGroupData" + :load-group-data="_loadGroupData" :load-group-page="loadGroupPage" :group-wrapper-change-page="groupWrapperChangePage" :row-height="rowHeight" diff --git a/packages/nc-gui/components/smartsheet/grid/index.vue b/packages/nc-gui/components/smartsheet/grid/index.vue index 85d1c64c8a..ac27c227ca 100644 --- a/packages/nc-gui/components/smartsheet/grid/index.vue +++ b/packages/nc-gui/components/smartsheet/grid/index.vue @@ -20,7 +20,7 @@ import { ref, useSmartsheetStoreOrThrow, useViewData, - useViewGroupBy, + useViewGroupByOrThrow, } from '#imports' import type { Row } from '#imports' @@ -166,7 +166,7 @@ const toggleOptimisedQuery = () => { } const { rootGroup, groupBy, isGroupBy, loadGroups, loadGroupData, loadGroupPage, groupWrapperChangePage, redistributeRows } = - useViewGroupBy(view, xWhere) + useViewGroupByOrThrow() const coreWrapperRef = ref() diff --git a/packages/nc-gui/components/smartsheet/header/Menu.vue b/packages/nc-gui/components/smartsheet/header/Menu.vue index 6b5069c16a..444d09452f 100644 --- a/packages/nc-gui/components/smartsheet/header/Menu.vue +++ b/packages/nc-gui/components/smartsheet/header/Menu.vue @@ -56,7 +56,7 @@ const showDeleteColumnModal = ref(false) const { gridViewCols } = useViewColumnsOrThrow() -const { fieldsToGroupBy, groupByLimit } = useViewGroupBy(view) +const { fieldsToGroupBy, groupByLimit } = useViewGroupByOrThrow(view) const setAsDisplayValue = async () => { try { diff --git a/packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue b/packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue index 98c9cfb82d..15c49464e9 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue @@ -18,7 +18,7 @@ const meta = inject(MetaInj, ref()) const { showSystemFields, metaColumnById } = useViewColumnsOrThrow() -const { groupBy } = useViewGroupBy(activeView) +const { groupBy } = useViewGroupByOrThrow() const options = computed( () => diff --git a/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue b/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue index d1b48414aa..443be829bb 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/GroupByMenu.vue @@ -22,7 +22,7 @@ const isLocked = inject(IsLockedInj, ref(false)) const { gridViewCols, updateGridViewColumn, metaColumnById, showSystemFields } = useViewColumnsOrThrow() -const { fieldsToGroupBy, groupByLimit } = useViewGroupBy(view) +const { fieldsToGroupBy, groupByLimit } = useViewGroupByOrThrow() const { $e } = useNuxtApp() diff --git a/packages/nc-gui/components/tabs/Smartsheet.vue b/packages/nc-gui/components/tabs/Smartsheet.vue index bc4e2a23b1..9e9efa2b85 100644 --- a/packages/nc-gui/components/tabs/Smartsheet.vue +++ b/packages/nc-gui/components/tabs/Smartsheet.vue @@ -54,7 +54,7 @@ const { handleSidebarOpenOnMobileForNonViews } = useConfigStore() const { activeTableId } = storeToRefs(useTablesStore()) const { activeView, openedViewsTab, activeViewTitleOrId } = storeToRefs(useViewsStore()) -const { isGallery, isGrid, isForm, isKanban, isLocked, isMap, isCalendar } = useProvideSmartsheetStore(activeView, meta) +const { isGallery, isGrid, isForm, isKanban, isLocked, isMap, isCalendar, xWhere } = useProvideSmartsheetStore(activeView, meta) useSqlEditor() @@ -86,6 +86,7 @@ provide( ) useProvideViewColumns(activeView, meta, () => reloadViewDataEventHook?.trigger()) +useProvideViewGroupBy(activeView, meta, xWhere) const grid = ref() diff --git a/packages/nc-gui/composables/useViewGroupBy.ts b/packages/nc-gui/composables/useViewGroupBy.ts index d70e7d67d7..445a05f662 100644 --- a/packages/nc-gui/composables/useViewGroupBy.ts +++ b/packages/nc-gui/composables/useViewGroupBy.ts @@ -1,5 +1,5 @@ import { UITypes } from 'nocodb-sdk' -import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, ViewType } from 'nocodb-sdk' +import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, ViewType, TableType } from 'nocodb-sdk' import type { Ref } from 'vue' import { message } from 'ant-design-vue' import { extractSdkResponseErrorMsg } from '../utils' @@ -17,548 +17,560 @@ import type { Group, GroupNestedIn, Row } from '#imports' const excludedGroupingUidt = [UITypes.Attachment, UITypes.QrCode, UITypes.Barcode] -export const useViewGroupBy = (view: Ref, where?: ComputedRef) => { - const groupByLimit: number = 3 - - const { api } = useApi() - - const { appInfo } = useGlobal() - - const { base } = storeToRefs(useBase()) - - const { sharedView, fetchSharedViewData } = useSharedView() - - const meta = inject(MetaInj) - - const { gridViewCols } = useViewColumnsOrThrow() - - const { getMeta } = useMetas() - - const sharedViewPassword = inject(SharedViewPasswordInj, ref(null)) - - const groupBy = computed<{ column: ColumnType; sort: string; order?: number }[]>(() => { - const tempGroupBy: { column: ColumnType; sort: string; order?: number }[] = [] - Object.values(gridViewCols.value).forEach((col) => { - if (col.group_by) { - const column = meta?.value?.columns?.find((f) => f.id === col.fk_column_id) - if (column) { - tempGroupBy.push({ - column, - sort: col.group_by_sort || 'asc', - order: col.group_by_order || 1, - }) +const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( + ( + view: Ref, + meta: Ref | ComputedRef, + where?: ComputedRef, + isPublic = false, + ) => { + const groupByLimit: number = 3 + + const { api } = useApi() + + const { appInfo } = useGlobal() + + const { base } = storeToRefs(useBase()) + + const { sharedView, fetchSharedViewData } = useSharedView() + + const { gridViewCols } = useViewColumnsOrThrow() + + const { getMeta } = useMetas() + + const sharedViewPassword = inject(SharedViewPasswordInj, ref(null)) + + const groupBy = computed<{ column: ColumnType; sort: string; order?: number }[]>(() => { + const tempGroupBy: { column: ColumnType; sort: string; order?: number }[] = [] + Object.values(gridViewCols.value).forEach((col) => { + if (col.group_by) { + const column = meta?.value?.columns?.find((f) => f.id === col.fk_column_id) + if (column) { + tempGroupBy.push({ + column, + sort: col.group_by_sort || 'asc', + order: col.group_by_order || 1, + }) + } } - } + }) + tempGroupBy.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) + return tempGroupBy }) - tempGroupBy.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) - return tempGroupBy - }) - - const isGroupBy = computed(() => !!groupBy.value.length) - - const { isUIAllowed } = useRoles() - - const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() - - const isPublic = inject(IsPublicInj, ref(false)) - - const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook()) - - const groupByGroupLimit = computed(() => { - return appInfo.value.defaultGroupByLimit?.limitGroup || 10 - }) - - const groupByRecordLimit = computed(() => { - return appInfo.value.defaultGroupByLimit?.limitRecord || 10 - }) - - const supportedLookups = ref([]) - - const fieldsToGroupBy = computed(() => - (meta?.value?.columns || []).filter((field) => { - if (excludedGroupingUidt.includes(field.uidt as UITypes)) return false - - if (field.uidt === UITypes.Lookup) { - return field.id && supportedLookups.value.includes(field.id) - } - - return true - }), - ) - - const rootGroup = ref({ - key: 'root', - color: 'root', - count: 0, - column: {} as any, - nestedIn: [], - paginationData: { page: 1, pageSize: groupByGroupLimit.value }, - nested: true, - children: [], - root: true, - }) - - async function groupWrapperChangePage(page: number, groupWrapper?: Group) { - groupWrapper = groupWrapper || rootGroup.value - - if (!groupWrapper) return - - groupWrapper.paginationData.page = page - await loadGroups( - { - offset: (page - 1) * (groupWrapper.paginationData.pageSize || groupByGroupLimit.value), - } as any, - groupWrapper, + + const isGroupBy = computed(() => !!groupBy.value.length) + + const { isUIAllowed } = useRoles() + + const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() + + const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook()) + + const groupByGroupLimit = computed(() => { + return appInfo.value.defaultGroupByLimit?.limitGroup || 10 + }) + + const groupByRecordLimit = computed(() => { + return appInfo.value.defaultGroupByLimit?.limitRecord || 10 + }) + + const supportedLookups = ref([]) + + const fieldsToGroupBy = computed(() => + (meta?.value?.columns || []).filter((field) => { + if (excludedGroupingUidt.includes(field.uidt as UITypes)) return false + + if (field.uidt === UITypes.Lookup) { + return field.id && supportedLookups.value.includes(field.id) + } + + return true + }), ) - } - - const formatData = (list: Record[]) => - list.map((row) => ({ - row: { ...row }, - oldRow: { ...row }, - rowMeta: {}, - })) - - const valueToTitle = (value: string, col: ColumnType) => { - if (col.uidt === UITypes.Checkbox) { - return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE + + const rootGroup = ref({ + key: 'root', + color: 'root', + count: 0, + column: {} as any, + nestedIn: [], + paginationData: { page: 1, pageSize: groupByGroupLimit.value }, + nested: true, + children: [], + root: true, + }) + + async function groupWrapperChangePage(page: number, groupWrapper?: Group) { + groupWrapper = groupWrapper || rootGroup.value + + if (!groupWrapper) return + + groupWrapper.paginationData.page = page + await loadGroups( + { + offset: (page - 1) * (groupWrapper.paginationData.pageSize || groupByGroupLimit.value), + } as any, + groupWrapper, + ) } - - if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes)) { - if (!value) { - return GROUP_BY_VARS.NULL + + const formatData = (list: Record[]) => + list.map((row) => ({ + row: { ...row }, + oldRow: { ...row }, + rowMeta: {}, + })) + + const valueToTitle = (value: string, col: ColumnType) => { + if (col.uidt === UITypes.Checkbox) { + return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE } + + if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes)) { + if (!value) { + return GROUP_BY_VARS.NULL + } + } + + // convert to JSON string if non-string value + if (value && typeof value === 'object') { + value = JSON.stringify(value) + } + + return value ?? GROUP_BY_VARS.NULL } - - // convert to JSON string if non-string value - if (value && typeof value === 'object') { - value = JSON.stringify(value) - } - - return value ?? GROUP_BY_VARS.NULL - } - - const colors = ref(enumColor.light) - - const nextGroupColor = ref(colors.value[0]) - - const getNextColor = () => { - const tempColor = nextGroupColor.value - const index = colors.value.indexOf(nextGroupColor.value) - if (index === colors.value.length - 1) { - nextGroupColor.value = colors.value[0] - } else { - nextGroupColor.value = colors.value[index + 1] + + const colors = ref(enumColor.light) + + const nextGroupColor = ref(colors.value[0]) + + const getNextColor = () => { + const tempColor = nextGroupColor.value + const index = colors.value.indexOf(nextGroupColor.value) + if (index === colors.value.length - 1) { + nextGroupColor.value = colors.value[0] + } else { + nextGroupColor.value = colors.value[index + 1] + } + return tempColor } - return tempColor - } - - const findKeyColor = (key?: string, col?: ColumnType): string => { - if (col) { - switch (col.uidt) { - case UITypes.MultiSelect: { - const keys = key?.split(',') || [] - const colors = [] - for (const k of keys) { - const option = (col.colOptions as SelectOptionsType).options?.find((o) => o.title === k) + + const findKeyColor = (key?: string, col?: ColumnType): string => { + if (col) { + switch (col.uidt) { + case UITypes.MultiSelect: { + const keys = key?.split(',') || [] + const colors = [] + for (const k of keys) { + const option = (col.colOptions as SelectOptionsType).options?.find((o) => o.title === k) + if (option) { + colors.push(option.color) + } + } + return colors.join(',') + } + case UITypes.SingleSelect: { + const option = (col.colOptions as SelectOptionsType).options?.find((o) => o.title === key) if (option) { - colors.push(option.color) + return option.color || getNextColor() } + return 'gray' } - return colors.join(',') - } - case UITypes.SingleSelect: { - const option = (col.colOptions as SelectOptionsType).options?.find((o) => o.title === key) - if (option) { - return option.color || getNextColor() + case UITypes.Checkbox: { + if (key) { + return themeColors.success + } + return themeColors.error } - return 'gray' + default: + return key ? getNextColor() : 'gray' } - case UITypes.Checkbox: { - if (key) { - return themeColors.success + } + return key ? getNextColor() : 'gray' + } + + const calculateNestedWhere = (nestedIn: GroupNestedIn[], existing = '') => { + return nestedIn.reduce((acc, curr) => { + if (curr.key === GROUP_BY_VARS.NULL) { + acc += `${acc.length ? '~and' : ''}(${curr.title},gb_null)` + } else if (curr.column_uidt === UITypes.Checkbox) { + acc += `${acc.length ? '~and' : ''}(${curr.title},${curr.key === GROUP_BY_VARS.TRUE ? 'checked' : 'notchecked'})` + } else if ( + [UITypes.Date, UITypes.DateTime, UITypes.CreatedTime, UITypes.LastModifiedTime].includes(curr.column_uidt as UITypes) + ) { + acc += `${acc.length ? '~and' : ''}(${curr.title},eq,exactDate,${curr.key})` + } else if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(curr.column_uidt as UITypes)) { + try { + const value = JSON.parse(curr.key) + acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${(Array.isArray(value) ? value : [value]) + .map((v: any) => v.id) + .join(',')})` + } catch (e) { + console.error(e) } - return themeColors.error + } else { + acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${curr.key})` } - default: - return key ? getNextColor() : 'gray' - } + return acc + }, existing) } - return key ? getNextColor() : 'gray' - } - - const calculateNestedWhere = (nestedIn: GroupNestedIn[], existing = '') => { - return nestedIn.reduce((acc, curr) => { - if (curr.key === GROUP_BY_VARS.NULL) { - acc += `${acc.length ? '~and' : ''}(${curr.title},gb_null)` - } else if (curr.column_uidt === UITypes.Checkbox) { - acc += `${acc.length ? '~and' : ''}(${curr.title},${curr.key === GROUP_BY_VARS.TRUE ? 'checked' : 'notchecked'})` - } else if ( - [UITypes.Date, UITypes.DateTime, UITypes.CreatedTime, UITypes.LastModifiedTime].includes(curr.column_uidt as UITypes) - ) { - acc += `${acc.length ? '~and' : ''}(${curr.title},eq,exactDate,${curr.key})` - } else if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(curr.column_uidt as UITypes)) { - try { - const value = JSON.parse(curr.key) - acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${(Array.isArray(value) ? value : [value]) - .map((v: any) => v.id) - .join(',')})` - } catch (e) { - console.error(e) + + async function loadGroups(params: any = {}, group?: Group) { + try { + group = group || rootGroup.value + + if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id || !group) return + + if (groupBy.value.length === 0) { + group.children = [] + return } - } else { - acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${curr.key})` - } - return acc - }, existing) - } - - async function loadGroups(params: any = {}, group?: Group) { - try { - group = group || rootGroup.value - - if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id || !group) return - - if (groupBy.value.length === 0) { - group.children = [] - return - } - - if (group.nestedIn.length > groupBy.value.length) return - - if (group.nestedIn.length === 0) nextGroupColor.value = colors.value[0] - const groupby = groupBy.value[group.nestedIn.length] - - const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) - if (!groupby || !groupby.column.title) return - - if (isPublic.value && !sharedView.value?.uuid) { - return - } - - const response = !isPublic.value - ? await api.dbViewRow.groupBy('noco', base.value.id, view.value.fk_model_id, view.value.id, { - offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value), - limit: group.paginationData.pageSize ?? groupByGroupLimit.value, - ...params, - ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), - ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), - where: `${nestedWhere}`, - sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, - column_name: groupby.column.title, - } as any) - : await api.public.dataGroupBy( - sharedView.value!.uuid!, - { + + if (group.nestedIn.length > groupBy.value.length) return + + if (group.nestedIn.length === 0) nextGroupColor.value = colors.value[0] + const groupby = groupBy.value[group.nestedIn.length] + + const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) + if (!groupby || !groupby.column.title) return + + if (isPublic && !sharedView.value?.uuid) { + return + } + + const response = !isPublic + ? await api.dbViewRow.groupBy('noco', base.value.id, view.value.fk_model_id, view.value.id, { offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value), limit: group.paginationData.pageSize ?? groupByGroupLimit.value, ...params, - where: nestedWhere, + ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), + ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), + where: `${nestedWhere}`, sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, column_name: groupby.column.title, - sortsArr: sorts.value, - filtersArr: nestedFilters.value, - }, - { - headers: { - 'xc-password': sharedViewPassword.value, + } as any) + : await api.public.dataGroupBy( + sharedView.value!.uuid!, + { + offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value), + limit: group.paginationData.pageSize ?? groupByGroupLimit.value, + ...params, + where: nestedWhere, + sort: `${groupby.sort === 'desc' ? '-' : ''}${groupby.column.title}`, + column_name: groupby.column.title, + sortsArr: sorts.value, + filtersArr: nestedFilters.value, }, - }, + { + headers: { + 'xc-password': sharedViewPassword.value, + }, + }, + ) + + const tempList: Group[] = response.list.reduce((acc: Group[], curr: Record) => { + const keyExists = acc.find( + (a) => a.key === valueToTitle(curr[groupby.column.column_name!] ?? curr[groupby.column.title!], groupby.column), ) - - const tempList: Group[] = response.list.reduce((acc: Group[], curr: Record) => { - const keyExists = acc.find( - (a) => a.key === valueToTitle(curr[groupby.column.column_name!] ?? curr[groupby.column.title!], groupby.column), - ) - if (keyExists) { - keyExists.count += +curr.count - keyExists.paginationData = { page: 1, pageSize: groupByGroupLimit.value, totalRows: keyExists.count } + if (keyExists) { + keyExists.count += +curr.count + keyExists.paginationData = { page: 1, pageSize: groupByGroupLimit.value, totalRows: keyExists.count } + return acc + } + if (groupby.column.title && groupby.column.uidt) { + acc.push({ + key: valueToTitle(curr[groupby.column.title!], groupby.column), + column: groupby.column, + count: +curr.count, + color: findKeyColor(curr[groupby.column.title!], groupby.column), + nestedIn: [ + ...group!.nestedIn, + { + title: groupby.column.title, + column_name: groupby.column.title!, + key: valueToTitle(curr[groupby.column.title!], groupby.column), + column_uidt: groupby.column.uidt, + }, + ], + paginationData: { + page: 1, + pageSize: group!.nestedIn.length < groupBy.value.length - 1 ? groupByGroupLimit.value : groupByRecordLimit.value, + totalRows: +curr.count, + }, + nested: group!.nestedIn.length < groupBy.value.length - 1, + }) + } return acc + }, []) + + if (!group.children) group.children = [] + + for (const temp of tempList) { + const keyExists = group.children?.find((a) => a.key === temp.key) + if (keyExists) { + temp.paginationData = { + page: keyExists.paginationData.page || temp.paginationData.page, + pageSize: keyExists.paginationData.pageSize || temp.paginationData.pageSize, + totalRows: temp.count, + } + temp.color = keyExists.color + // update group + Object.assign(keyExists, temp) + continue + } + group.children.push(temp) } - if (groupby.column.title && groupby.column.uidt) { - acc.push({ - key: valueToTitle(curr[groupby.column.title!], groupby.column), - column: groupby.column, - count: +curr.count, - color: findKeyColor(curr[groupby.column.title!], groupby.column), - nestedIn: [ - ...group!.nestedIn, - { - title: groupby.column.title, - column_name: groupby.column.title!, - key: valueToTitle(curr[groupby.column.title!], groupby.column), - column_uidt: groupby.column.uidt, - }, - ], - paginationData: { - page: 1, - pageSize: group!.nestedIn.length < groupBy.value.length - 1 ? groupByGroupLimit.value : groupByRecordLimit.value, - totalRows: +curr.count, - }, - nested: group!.nestedIn.length < groupBy.value.length - 1, + + // clear rest of the children + group.children = group.children.filter((c) => tempList.find((t) => t.key === c.key)) + + if (group.count <= (group.paginationData.pageSize ?? groupByGroupLimit.value)) { + group.children.sort((a, b) => { + const orderA = tempList.findIndex((t) => t.key === a.key) + const orderB = tempList.findIndex((t) => t.key === b.key) + return orderA - orderB }) } - return acc - }, []) - - if (!group.children) group.children = [] - - for (const temp of tempList) { - const keyExists = group.children?.find((a) => a.key === temp.key) - if (keyExists) { - temp.paginationData = { - page: keyExists.paginationData.page || temp.paginationData.page, - pageSize: keyExists.paginationData.pageSize || temp.paginationData.pageSize, - totalRows: temp.count, - } - temp.color = keyExists.color - // update group - Object.assign(keyExists, temp) - continue + + group.paginationData = response.pageInfo + + // to cater the case like when querying with a non-zero offset + // the result page may point to the target page where the actual returned data don't display on + const expectedPage = Math.max(1, Math.ceil(group.paginationData.totalRows! / group.paginationData.pageSize!)) + if (expectedPage < group.paginationData.page!) { + await groupWrapperChangePage(expectedPage, group) } - group.children.push(temp) - } - - // clear rest of the children - group.children = group.children.filter((c) => tempList.find((t) => t.key === c.key)) - - if (group.count <= (group.paginationData.pageSize ?? groupByGroupLimit.value)) { - group.children.sort((a, b) => { - const orderA = tempList.findIndex((t) => t.key === a.key) - const orderB = tempList.findIndex((t) => t.key === b.key) - return orderA - orderB - }) + } catch (e) { + message.error(await extractSdkResponseErrorMsg(e)) } - - group.paginationData = response.pageInfo - - // to cater the case like when querying with a non-zero offset - // the result page may point to the target page where the actual returned data don't display on - const expectedPage = Math.max(1, Math.ceil(group.paginationData.totalRows! / group.paginationData.pageSize!)) - if (expectedPage < group.paginationData.page!) { - await groupWrapperChangePage(expectedPage, group) + } + + async function loadGroupData(group: Group, force = false, params: any = {}) { + try { + if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id) return + + if (group.children && !force) return + + if (!group.paginationData) { + group.paginationData = { page: 1, pageSize: groupByRecordLimit.value } + } + + const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) + + const query = { + offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByRecordLimit.value), + limit: group.paginationData.pageSize ?? groupByRecordLimit.value, + where: `${nestedWhere}`, + } + + const response = !isPublic + ? await api.dbViewRow.list('noco', base.value.id, view.value.fk_model_id, view.value.id, { + ...query, + ...params, + ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), + ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), + } as any) + : await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query }) + + group.count = response.pageInfo.totalRows ?? 0 + group.rows = formatData(response.list) + group.paginationData = response.pageInfo + } catch (e) { + message.error(await extractSdkResponseErrorMsg(e)) } - } catch (e) { - message.error(await extractSdkResponseErrorMsg(e)) } - } - - async function loadGroupData(group: Group, force = false, params: any = {}) { - try { - if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id) return - - if (group.children && !force) return - + + const loadGroupPage = async (group: Group, p: number) => { if (!group.paginationData) { group.paginationData = { page: 1, pageSize: groupByRecordLimit.value } } - - const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) - - const query = { - offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByRecordLimit.value), - limit: group.paginationData.pageSize ?? groupByRecordLimit.value, - where: `${nestedWhere}`, - } - - const response = !isPublic.value - ? await api.dbViewRow.list('noco', base.value.id, view.value.fk_model_id, view.value.id, { - ...query, - ...params, - ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), - ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), - } as any) - : await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query }) - - group.count = response.pageInfo.totalRows ?? 0 - group.rows = formatData(response.list) - group.paginationData = response.pageInfo - } catch (e) { - message.error(await extractSdkResponseErrorMsg(e)) - } - } - - const loadGroupPage = async (group: Group, p: number) => { - if (!group.paginationData) { - group.paginationData = { page: 1, pageSize: groupByRecordLimit.value } + group.paginationData.page = p + await loadGroupData(group, true) } - group.paginationData.page = p - await loadGroupData(group, true) - } - - const refreshNested = (group?: Group, nestLevel = 0) => { - group = group || rootGroup.value - if (!group) return - - if (nestLevel < groupBy.value.length) { - group.nested = true - } else { - group.nested = false - } - - if (group.nested) { - if (group?.rows) { - group.rows = [] + + const refreshNested = (group?: Group, nestLevel = 0) => { + group = group || rootGroup.value + if (!group) return + + if (nestLevel < groupBy.value.length) { + group.nested = true + } else { + group.nested = false } - } else { - if (group?.children) { - group.children = [] + + if (group.nested) { + if (group?.rows) { + group.rows = [] + } + } else { + if (group?.children) { + group.children = [] + } } - } - - if (nestLevel > groupBy.value.length) return - - for (const child of group.children || []) { - refreshNested(child, nestLevel + 1) - } - } - - watch( - () => groupBy.value.length, - async () => { - if (groupBy.value.length > 0) { - rootGroup.value.paginationData = { page: 1, pageSize: groupByGroupLimit.value } - rootGroup.value.column = {} as any - await loadGroups() - refreshNested() - nextTick(() => reloadViewDataHook?.trigger()) + + if (nestLevel > groupBy.value.length) return + + for (const child of group.children || []) { + refreshNested(child, nestLevel + 1) } - }, - { immediate: true }, - ) - - const findGroupByNestedIn = (nestedIn: GroupNestedIn[], group?: Group, nestLevel = 0): Group => { - group = group || rootGroup.value - if (nestLevel >= nestedIn.length) return group - const child = group.children?.find((g) => g.key === nestedIn[nestLevel].key) - if (child) { - if (child.nested) { - return findGroupByNestedIn(nestedIn, child, nestLevel + 1) + } + + watch( + () => groupBy.value.length, + async () => { + if (groupBy.value.length > 0) { + rootGroup.value.paginationData = { page: 1, pageSize: groupByGroupLimit.value } + rootGroup.value.column = {} as any + await loadGroups() + refreshNested() + nextTick(() => reloadViewDataHook?.trigger()) + } + }, + { immediate: true }, + ) + + const findGroupByNestedIn = (nestedIn: GroupNestedIn[], group?: Group, nestLevel = 0): Group => { + group = group || rootGroup.value + if (nestLevel >= nestedIn.length) return group + const child = group.children?.find((g) => g.key === nestedIn[nestLevel].key) + if (child) { + if (child.nested) { + return findGroupByNestedIn(nestedIn, child, nestLevel + 1) + } + return child } - return child + return group } - return group - } - - const parentGroup = (group: Group) => { - const parent = findGroupByNestedIn(group.nestedIn.slice(0, -1)) - return parent - } - - const modifyCount = (group: Group, countEffect: number) => { - if (!group) return - group.count += countEffect - // remove group if count is 0 - if (group.count === 0) { - const parent = parentGroup(group) - if (parent) { - parent.children = parent.children?.filter((c) => c.key !== group.key) + + const parentGroup = (group: Group) => { + const parent = findGroupByNestedIn(group.nestedIn.slice(0, -1)) + return parent + } + + const modifyCount = (group: Group, countEffect: number) => { + if (!group) return + group.count += countEffect + // remove group if count is 0 + if (group.count === 0) { + const parent = parentGroup(group) + if (parent) { + parent.children = parent.children?.filter((c) => c.key !== group.key) + } } + if (group.root) return + modifyCount(parentGroup(group), countEffect) } - if (group.root) return - modifyCount(parentGroup(group), countEffect) - } - - const findGroupForRow = (row: Row, group?: Group, nestLevel = 0): { found: boolean; group: Group } => { - group = group || rootGroup.value - if (group.nested) { - const child = group.children?.find((g) => { - if (!groupBy.value[nestLevel].column.title) return undefined - return g.key === valueToTitle(row.row[groupBy.value[nestLevel].column.title!], groupBy.value[nestLevel].column) - }) - if (child) { - return findGroupForRow(row, child, nestLevel + 1) + + const findGroupForRow = (row: Row, group?: Group, nestLevel = 0): { found: boolean; group: Group } => { + group = group || rootGroup.value + if (group.nested) { + const child = group.children?.find((g) => { + if (!groupBy.value[nestLevel].column.title) return undefined + return g.key === valueToTitle(row.row[groupBy.value[nestLevel].column.title!], groupBy.value[nestLevel].column) + }) + if (child) { + return findGroupForRow(row, child, nestLevel + 1) + } + return { found: false, group } } - return { found: false, group } + return { found: true, group } } - return { found: true, group } - } - - const redistributeRows = (group?: Group) => { - group = group || rootGroup.value - if (!group) return - if (!group.nested && group.rows) { - group.rows.forEach((row) => { - const properGroup = findGroupForRow(row) - if (properGroup.found) { - if (properGroup.group !== group) { - if (properGroup.group) { - properGroup.group.rows?.push(row) - modifyCount(properGroup.group, 1) + + const redistributeRows = (group?: Group) => { + group = group || rootGroup.value + if (!group) return + if (!group.nested && group.rows) { + group.rows.forEach((row) => { + const properGroup = findGroupForRow(row) + if (properGroup.found) { + if (properGroup.group !== group) { + if (properGroup.group) { + properGroup.group.rows?.push(row) + modifyCount(properGroup.group, 1) + } + if (group) { + group.rows?.splice(group!.rows.indexOf(row), 1) + modifyCount(group, -1) + } } + } else { if (group) { group.rows?.splice(group!.rows.indexOf(row), 1) modifyCount(group, -1) } + if (properGroup.group?.children) loadGroups({}, properGroup.group) } - } else { - if (group) { - group.rows?.splice(group!.rows.indexOf(row), 1) - modifyCount(group, -1) - } - if (properGroup.group?.children) loadGroups({}, properGroup.group) - } - }) - } else { - group.children?.forEach((g) => redistributeRows(g)) + }) + } else { + group.children?.forEach((g) => redistributeRows(g)) + } } - } - - const loadAllowedLookups = async () => { - const filteredLookupCols = [] - try { - for (const col of meta?.value?.columns || []) { - if (col.uidt !== UITypes.Lookup) continue - - let nextCol: ColumnType = col - // check the lookup column is supported type or not - while (nextCol && nextCol.uidt === UITypes.Lookup) { - const lookupRelation = (await getMeta(nextCol.fk_model_id as string))?.columns?.find( - (c) => c.id === (nextCol?.colOptions as LookupType).fk_relation_column_id, - ) - - const relatedTableMeta = await getMeta( - (lookupRelation?.colOptions as LinkToAnotherRecordType).fk_related_model_id as string, - ) - - nextCol = relatedTableMeta?.columns?.find( - (c) => c.id === ((nextCol?.colOptions as LookupType).fk_lookup_column_id as string), - ) as ColumnType - - // if next column is same as root lookup column then break the loop - // since it's going to be a circular loop, and ignore the column - if (nextCol?.id === col.id) { - break + + const loadAllowedLookups = async () => { + const filteredLookupCols = [] + try { + for (const col of meta?.value?.columns || []) { + if (col.uidt !== UITypes.Lookup) continue + + let nextCol: ColumnType = col + // check the lookup column is supported type or not + while (nextCol && nextCol.uidt === UITypes.Lookup) { + const lookupRelation = (await getMeta(nextCol.fk_model_id as string))?.columns?.find( + (c) => c.id === (nextCol?.colOptions as LookupType).fk_relation_column_id, + ) + + const relatedTableMeta = await getMeta( + (lookupRelation?.colOptions as LinkToAnotherRecordType).fk_related_model_id as string, + ) + + nextCol = relatedTableMeta?.columns?.find( + (c) => c.id === ((nextCol?.colOptions as LookupType).fk_lookup_column_id as string), + ) as ColumnType + + // if next column is same as root lookup column then break the loop + // since it's going to be a circular loop, and ignore the column + if (nextCol?.id === col.id) { + break + } } + + if (nextCol?.uidt !== UITypes.Attachment && col.id) filteredLookupCols.push(col.id) } - - if (nextCol?.uidt !== UITypes.Attachment && col.id) filteredLookupCols.push(col.id) + + supportedLookups.value = filteredLookupCols + } catch (e) { + console.error(e) } - - supportedLookups.value = filteredLookupCols - } catch (e) { - console.error(e) } - } - - onMounted(async () => { - await loadAllowedLookups() - }) + + onMounted(async () => { + await loadAllowedLookups() + }) + + watch(meta, async () => { + await loadAllowedLookups() + }) + + return { + rootGroup, + groupBy, + isGroupBy, + fieldsToGroupBy, + groupByLimit, + loadGroups, + loadGroupData, + loadGroupPage, + groupWrapperChangePage, + redistributeRows, + } + }, + 'useViewGroupBy', +) - watch(meta, async () => { - await loadAllowedLookups() - }) +export { useProvideViewGroupBy } - return { - rootGroup, - groupBy, - isGroupBy, - fieldsToGroupBy, - groupByLimit, - loadGroups, - loadGroupData, - loadGroupPage, - groupWrapperChangePage, - redistributeRows, - } +export function useViewGroupByOrThrow() { + const viewColumns = useViewGroupBy() + if (viewColumns == null) throw new Error('Please call `useProvideViewGroupBy` on the appropriate parent component') + return viewColumns } From 1a1d81a7920e228334373684aabff169754d3dc4 Mon Sep 17 00:00:00 2001 From: mertmit Date: Sun, 28 Apr 2024 02:41:50 +0000 Subject: [PATCH 10/46] fix: group by reload --- .../components/smartsheet/grid/GroupBy.vue | 23 ++- packages/nc-gui/composables/useViewGroupBy.ts | 161 +++++++++--------- 2 files changed, 93 insertions(+), 91 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue index 3e1911a16a..b6a6146e88 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue @@ -108,16 +108,21 @@ onBeforeUnmount(async () => { reloadViewDataHook?.off(reloadViewDataHandler) }) -watch( - [() => vGroup.value.key], - async (n, o) => { - if (n !== o) { - if (!vGroup.value.nested) { - await _loadGroupData(vGroup.value, true) - } +watch([() => vGroup.value.key], async (n, o) => { + if (n !== o) { + if (!vGroup.value.nested) { + await _loadGroupData(vGroup.value, true) + } else if (vGroup.value.nested) { + await props.loadGroups({}, vGroup.value) } - }, -) + } +}) + +onMounted(async () => { + if (vGroup.value.root === true) { + await props.loadGroups({}, vGroup.value) + } +}) if (vGroup.value.root === true) provide(ScrollParentInj, wrapper) diff --git a/packages/nc-gui/composables/useViewGroupBy.ts b/packages/nc-gui/composables/useViewGroupBy.ts index 445a05f662..b2f08ae3d6 100644 --- a/packages/nc-gui/composables/useViewGroupBy.ts +++ b/packages/nc-gui/composables/useViewGroupBy.ts @@ -27,19 +27,19 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( const groupByLimit: number = 3 const { api } = useApi() - + const { appInfo } = useGlobal() - + const { base } = storeToRefs(useBase()) - + const { sharedView, fetchSharedViewData } = useSharedView() - + const { gridViewCols } = useViewColumnsOrThrow() - + const { getMeta } = useMetas() - + const sharedViewPassword = inject(SharedViewPasswordInj, ref(null)) - + const groupBy = computed<{ column: ColumnType; sort: string; order?: number }[]>(() => { const tempGroupBy: { column: ColumnType; sort: string; order?: number }[] = [] Object.values(gridViewCols.value).forEach((col) => { @@ -57,37 +57,37 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( tempGroupBy.sort((a, b) => (a.order ?? Infinity) - (b.order ?? Infinity)) return tempGroupBy }) - + const isGroupBy = computed(() => !!groupBy.value.length) - + const { isUIAllowed } = useRoles() - + const { sorts, nestedFilters } = useSmartsheetStoreOrThrow() - + const reloadViewDataHook = inject(ReloadViewDataHookInj, createEventHook()) - + const groupByGroupLimit = computed(() => { return appInfo.value.defaultGroupByLimit?.limitGroup || 10 }) - + const groupByRecordLimit = computed(() => { return appInfo.value.defaultGroupByLimit?.limitRecord || 10 }) - + const supportedLookups = ref([]) - + const fieldsToGroupBy = computed(() => (meta?.value?.columns || []).filter((field) => { if (excludedGroupingUidt.includes(field.uidt as UITypes)) return false - + if (field.uidt === UITypes.Lookup) { return field.id && supportedLookups.value.includes(field.id) } - + return true }), ) - + const rootGroup = ref({ key: 'root', color: 'root', @@ -99,12 +99,12 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( children: [], root: true, }) - + async function groupWrapperChangePage(page: number, groupWrapper?: Group) { groupWrapper = groupWrapper || rootGroup.value - + if (!groupWrapper) return - + groupWrapper.paginationData.page = page await loadGroups( { @@ -113,37 +113,37 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( groupWrapper, ) } - + const formatData = (list: Record[]) => list.map((row) => ({ row: { ...row }, oldRow: { ...row }, rowMeta: {}, })) - + const valueToTitle = (value: string, col: ColumnType) => { if (col.uidt === UITypes.Checkbox) { return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE } - + if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes)) { if (!value) { return GROUP_BY_VARS.NULL } } - + // convert to JSON string if non-string value if (value && typeof value === 'object') { value = JSON.stringify(value) } - + return value ?? GROUP_BY_VARS.NULL } - + const colors = ref(enumColor.light) - + const nextGroupColor = ref(colors.value[0]) - + const getNextColor = () => { const tempColor = nextGroupColor.value const index = colors.value.indexOf(nextGroupColor.value) @@ -154,7 +154,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } return tempColor } - + const findKeyColor = (key?: string, col?: ColumnType): string => { if (col) { switch (col.uidt) { @@ -188,7 +188,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } return key ? getNextColor() : 'gray' } - + const calculateNestedWhere = (nestedIn: GroupNestedIn[], existing = '') => { return nestedIn.reduce((acc, curr) => { if (curr.key === GROUP_BY_VARS.NULL) { @@ -214,30 +214,30 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( return acc }, existing) } - + async function loadGroups(params: any = {}, group?: Group) { try { group = group || rootGroup.value - + if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id || !group) return - + if (groupBy.value.length === 0) { group.children = [] return } - + if (group.nestedIn.length > groupBy.value.length) return - + if (group.nestedIn.length === 0) nextGroupColor.value = colors.value[0] const groupby = groupBy.value[group.nestedIn.length] - + const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) if (!groupby || !groupby.column.title) return - + if (isPublic && !sharedView.value?.uuid) { return } - + const response = !isPublic ? await api.dbViewRow.groupBy('noco', base.value.id, view.value.fk_model_id, view.value.id, { offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByGroupLimit.value), @@ -267,7 +267,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( }, }, ) - + const tempList: Group[] = response.list.reduce((acc: Group[], curr: Record) => { const keyExists = acc.find( (a) => a.key === valueToTitle(curr[groupby.column.column_name!] ?? curr[groupby.column.title!], groupby.column), @@ -302,9 +302,9 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } return acc }, []) - + if (!group.children) group.children = [] - + for (const temp of tempList) { const keyExists = group.children?.find((a) => a.key === temp.key) if (keyExists) { @@ -320,10 +320,10 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } group.children.push(temp) } - + // clear rest of the children group.children = group.children.filter((c) => tempList.find((t) => t.key === c.key)) - + if (group.count <= (group.paginationData.pageSize ?? groupByGroupLimit.value)) { group.children.sort((a, b) => { const orderA = tempList.findIndex((t) => t.key === a.key) @@ -331,9 +331,9 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( return orderA - orderB }) } - + group.paginationData = response.pageInfo - + // to cater the case like when querying with a non-zero offset // the result page may point to the target page where the actual returned data don't display on const expectedPage = Math.max(1, Math.ceil(group.paginationData.totalRows! / group.paginationData.pageSize!)) @@ -344,25 +344,25 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( message.error(await extractSdkResponseErrorMsg(e)) } } - + async function loadGroupData(group: Group, force = false, params: any = {}) { try { if (!base?.value?.id || !view.value?.id || !view.value?.fk_model_id) return - + if (group.children && !force) return - + if (!group.paginationData) { group.paginationData = { page: 1, pageSize: groupByRecordLimit.value } } - + const nestedWhere = calculateNestedWhere(group.nestedIn, where?.value) - + const query = { offset: ((group.paginationData.page ?? 0) - 1) * (group.paginationData.pageSize ?? groupByRecordLimit.value), limit: group.paginationData.pageSize ?? groupByRecordLimit.value, where: `${nestedWhere}`, } - + const response = !isPublic ? await api.dbViewRow.list('noco', base.value.id, view.value.fk_model_id, view.value.id, { ...query, @@ -371,7 +371,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), } as any) : await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query }) - + group.count = response.pageInfo.totalRows ?? 0 group.rows = formatData(response.list) group.paginationData = response.pageInfo @@ -379,7 +379,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( message.error(await extractSdkResponseErrorMsg(e)) } } - + const loadGroupPage = async (group: Group, p: number) => { if (!group.paginationData) { group.paginationData = { page: 1, pageSize: groupByRecordLimit.value } @@ -387,17 +387,17 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( group.paginationData.page = p await loadGroupData(group, true) } - + const refreshNested = (group?: Group, nestLevel = 0) => { group = group || rootGroup.value if (!group) return - + if (nestLevel < groupBy.value.length) { group.nested = true } else { group.nested = false } - + if (group.nested) { if (group?.rows) { group.rows = [] @@ -407,28 +407,26 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( group.children = [] } } - + if (nestLevel > groupBy.value.length) return - + for (const child of group.children || []) { refreshNested(child, nestLevel + 1) } } - + watch( () => groupBy.value.length, async () => { if (groupBy.value.length > 0) { rootGroup.value.paginationData = { page: 1, pageSize: groupByGroupLimit.value } rootGroup.value.column = {} as any - await loadGroups() refreshNested() nextTick(() => reloadViewDataHook?.trigger()) } }, - { immediate: true }, ) - + const findGroupByNestedIn = (nestedIn: GroupNestedIn[], group?: Group, nestLevel = 0): Group => { group = group || rootGroup.value if (nestLevel >= nestedIn.length) return group @@ -441,12 +439,12 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } return group } - + const parentGroup = (group: Group) => { const parent = findGroupByNestedIn(group.nestedIn.slice(0, -1)) return parent } - + const modifyCount = (group: Group, countEffect: number) => { if (!group) return group.count += countEffect @@ -460,7 +458,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( if (group.root) return modifyCount(parentGroup(group), countEffect) } - + const findGroupForRow = (row: Row, group?: Group, nestLevel = 0): { found: boolean; group: Group } => { group = group || rootGroup.value if (group.nested) { @@ -475,7 +473,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( } return { found: true, group } } - + const redistributeRows = (group?: Group) => { group = group || rootGroup.value if (!group) return @@ -505,52 +503,51 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState( group.children?.forEach((g) => redistributeRows(g)) } } - + const loadAllowedLookups = async () => { const filteredLookupCols = [] try { for (const col of meta?.value?.columns || []) { if (col.uidt !== UITypes.Lookup) continue - + let nextCol: ColumnType = col // check the lookup column is supported type or not while (nextCol && nextCol.uidt === UITypes.Lookup) { const lookupRelation = (await getMeta(nextCol.fk_model_id as string))?.columns?.find( (c) => c.id === (nextCol?.colOptions as LookupType).fk_relation_column_id, ) - + const relatedTableMeta = await getMeta( (lookupRelation?.colOptions as LinkToAnotherRecordType).fk_related_model_id as string, ) - + nextCol = relatedTableMeta?.columns?.find( (c) => c.id === ((nextCol?.colOptions as LookupType).fk_lookup_column_id as string), ) as ColumnType - + // if next column is same as root lookup column then break the loop // since it's going to be a circular loop, and ignore the column if (nextCol?.id === col.id) { break } } - + if (nextCol?.uidt !== UITypes.Attachment && col.id) filteredLookupCols.push(col.id) } - + supportedLookups.value = filteredLookupCols } catch (e) { console.error(e) } } - - onMounted(async () => { - await loadAllowedLookups() - }) - - watch(meta, async () => { - await loadAllowedLookups() + + watch([() => view?.value?.id, () => meta.value?.columns], async ([newViewId]) => { + // reload only if view belongs to current table + if (newViewId && view.value?.fk_model_id === meta.value?.id) { + await loadAllowedLookups() + } }) - + return { rootGroup, groupBy, From de5491e5411f0354193c1802aca3e7bc1defe2f2 Mon Sep 17 00:00:00 2001 From: mertmit Date: Sun, 28 Apr 2024 02:41:50 +0000 Subject: [PATCH 11/46] chore: lint --- packages/nc-gui/composables/useSharedFormViewStore.ts | 2 +- packages/nc-gui/composables/useViewGroupBy.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nc-gui/composables/useSharedFormViewStore.ts b/packages/nc-gui/composables/useSharedFormViewStore.ts index 1631c39fb7..1f18338155 100644 --- a/packages/nc-gui/composables/useSharedFormViewStore.ts +++ b/packages/nc-gui/composables/useSharedFormViewStore.ts @@ -217,7 +217,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share ((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || column.required) ) { obj.localState[column.title!] = { - required: fieldRequired(undefined, column.uidt === UITypes.Checkbox && column.required ? true : false), + required: fieldRequired(undefined, !!(column.uidt === UITypes.Checkbox && column.required)), } } else if ( isLinksOrLTAR(column) && diff --git a/packages/nc-gui/composables/useViewGroupBy.ts b/packages/nc-gui/composables/useViewGroupBy.ts index b2f08ae3d6..dafe5c8683 100644 --- a/packages/nc-gui/composables/useViewGroupBy.ts +++ b/packages/nc-gui/composables/useViewGroupBy.ts @@ -1,5 +1,5 @@ import { UITypes } from 'nocodb-sdk' -import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, ViewType, TableType } from 'nocodb-sdk' +import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk' import type { Ref } from 'vue' import { message } from 'ant-design-vue' import { extractSdkResponseErrorMsg } from '../utils' From 4277beaaff9973a4c2ce4dd25b52810ef66af8ce Mon Sep 17 00:00:00 2001 From: mertmit Date: Sun, 28 Apr 2024 02:41:50 +0000 Subject: [PATCH 12/46] fix: use key instead of index for group by panels --- packages/nc-gui/components/smartsheet/grid/GroupBy.vue | 6 +++--- .../playwright/tests/db/general/toolbarOperations.spec.ts | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue index b6a6146e88..0db4a55d09 100644 --- a/packages/nc-gui/components/smartsheet/grid/GroupBy.vue +++ b/packages/nc-gui/components/smartsheet/grid/GroupBy.vue @@ -77,7 +77,7 @@ const findAndLoadSubGroup = (key: any) => { if (key.length > 0 && vGroup.value.children) { if (!oldActiveGroups.value.includes(key[key.length - 1])) { const k = key[key.length - 1].replace('group-panel-', '') - const grp = vGroup.value.children[k] + const grp = vGroup.value.children.find((g) => `${g.key}` === k) if (grp) { if (grp.nested) { if (!grp.children?.length) props.loadGroups({}, grp) @@ -239,7 +239,7 @@ const shouldRenderCell = (column) => > diff --git a/tests/playwright/tests/db/general/toolbarOperations.spec.ts b/tests/playwright/tests/db/general/toolbarOperations.spec.ts index 6afa2b37ea..a7cb00afaf 100644 --- a/tests/playwright/tests/db/general/toolbarOperations.spec.ts +++ b/tests/playwright/tests/db/general/toolbarOperations.spec.ts @@ -230,7 +230,7 @@ test.describe('Toolbar operations (GRID)', () => { }); await toolbar.clickFilter(); - await dashboard.grid.groupPage.openGroup({ indexMap: [1, 0] }); + await dashboard.grid.groupPage.openGroup({ indexMap: [2, 0] }); await dashboard.grid.groupPage.verifyGroupHeader({ indexMap: [1, 0], @@ -377,16 +377,14 @@ test.describe('Toolbar operations (GRID)', () => { }); await toolbar.clickFilter(); - await dashboard.grid.groupPage.openGroup({ indexMap: [5, 0, 0] }); - await dashboard.grid.groupPage.verifyGroupHeader({ - indexMap: [5, 0, 0], + indexMap: [4, 0, 0], count: 2, title: 'ReleaseYear', }); await dashboard.grid.groupPage.verifyGroup({ - indexMap: [5, 0, 0], + indexMap: [4, 0, 0], value: '2006', }); From 6efbe449cc5e49b8b14ec5bf5d36105c64c4cb86 Mon Sep 17 00:00:00 2001 From: Mert E Date: Sun, 28 Apr 2024 06:13:02 +0300 Subject: [PATCH 13/46] fix: copyTable fallback to useCopy with plain text for insecure context (#8346) Signed-off-by: mertmit --- packages/nc-gui/composables/useMultiSelect/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 97e9d154eb..2301cab053 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/packages/nc-gui/composables/useMultiSelect/index.ts @@ -300,7 +300,10 @@ export function useMultiSelect( const blobHTML = new Blob([copyHTML], { type: 'text/html' }) const blobPlainText = new Blob([copyPlainText], { type: 'text/plain' }) - return navigator.clipboard.write([new ClipboardItem({ [blobHTML.type]: blobHTML, [blobPlainText.type]: blobPlainText })]) + return ( + navigator.clipboard?.write([new ClipboardItem({ [blobHTML.type]: blobHTML, [blobPlainText.type]: blobPlainText })]) ?? + copy(copyPlainText) + ) } async function copyValue(ctx?: Cell) { From fed1c7ba5c0b47a5f21c0962f6ef2b0de907d7a3 Mon Sep 17 00:00:00 2001 From: Mert E Date: Mon, 29 Apr 2024 11:52:21 +0300 Subject: [PATCH 14/46] feat: virtual scroll for grid (#8356) * feat: virtual scroll for grid * feat: improve virtual scroll * fix: remove unused expose & ref * feat: move row ltar helpers to parent level * fix: use shared composable for useMetas * fix: column add issue * fix: reload issue * feat: move cell state to computed * chore: lint * fix: null check for sticky field * fix: PR requested changes * fix: shared views * fix: provide row store calls * test: avoid all rows selector * fix: group by * fix: include isVirtualCol in cellMeta * fix: split colMeta and cellMeta * chore: lint * test: edit column flakiness * test: renderColumn for dashboard grid * test: user column test flakiness --- .../components/shared-view/Calendar.vue | 2 + .../nc-gui/components/shared-view/Gallery.vue | 2 + .../nc-gui/components/shared-view/Grid.vue | 2 + .../nc-gui/components/shared-view/Kanban.vue | 2 + .../nc-gui/components/shared-view/Map.vue | 2 + .../nc-gui/components/smartsheet/Cell.vue | 121 +-- .../nc-gui/components/smartsheet/Form.vue | 1 - packages/nc-gui/components/smartsheet/Row.vue | 17 +- .../smartsheet/SharedMapMarkerPopup.vue | 5 +- .../components/smartsheet/TableDataCell.vue | 4 +- .../components/smartsheet/VirtualCell.vue | 61 +- .../smartsheet/column/DefaultValue.vue | 4 +- .../smartsheet/expanded-form/index.vue | 2 + .../smartsheet/grid/GroupByTable.vue | 1 + .../components/smartsheet/grid/Table.vue | 909 ++++++++++++------ .../nc-gui/components/tabs/Smartsheet.vue | 5 +- .../composables/useExpandedFormStore.ts | 2 +- packages/nc-gui/composables/useMetas.ts | 4 +- .../composables/useMultiSelect/index.ts | 50 +- .../composables/useSharedFormViewStore.ts | 4 +- .../composables/useSmartsheetLtarHelpers.ts | 249 +++++ .../composables/useSmartsheetRowStore.ts | 266 +---- packages/nc-gui/lib/types.ts | 1 + .../pages/Dashboard/Grid/Column/index.ts | 3 +- .../playwright/pages/Dashboard/Grid/index.ts | 44 +- .../Dashboard/common/Cell/UserOptionCell.ts | 2 +- .../pages/Dashboard/common/Cell/index.ts | 1 + .../pages/Dashboard/common/Topbar/index.ts | 1 + .../tests/db/columns/columnUserSelect.spec.ts | 6 +- .../db/features/keyboardShortcuts.spec.ts | 3 + 30 files changed, 1071 insertions(+), 705 deletions(-) create mode 100644 packages/nc-gui/composables/useSmartsheetLtarHelpers.ts diff --git a/packages/nc-gui/components/shared-view/Calendar.vue b/packages/nc-gui/components/shared-view/Calendar.vue index 83e91cd2a7..1e22a4160a 100644 --- a/packages/nc-gui/components/shared-view/Calendar.vue +++ b/packages/nc-gui/components/shared-view/Calendar.vue @@ -28,6 +28,8 @@ provide(IsPublicInj, ref(true)) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) +useProvideSmartsheetLtarHelpers(meta) + useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideKanbanViewStore(meta, sharedView) diff --git a/packages/nc-gui/components/shared-view/Gallery.vue b/packages/nc-gui/components/shared-view/Gallery.vue index 890c52f229..7de66fdc71 100644 --- a/packages/nc-gui/components/shared-view/Gallery.vue +++ b/packages/nc-gui/components/shared-view/Gallery.vue @@ -32,6 +32,8 @@ provide(IsPublicInj, ref(true)) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) +useProvideSmartsheetLtarHelpers(meta) + useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideKanbanViewStore(meta, sharedView) diff --git a/packages/nc-gui/components/shared-view/Grid.vue b/packages/nc-gui/components/shared-view/Grid.vue index 7e14057284..85fd1ff4c4 100644 --- a/packages/nc-gui/components/shared-view/Grid.vue +++ b/packages/nc-gui/components/shared-view/Grid.vue @@ -43,6 +43,8 @@ provide(IsLockedInj, isLocked) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) useProvideViewGroupBy(sharedView, meta, xWhere, true) +useProvideSmartsheetLtarHelpers(meta) + if (signedIn.value) { try { await loadProject() diff --git a/packages/nc-gui/components/shared-view/Kanban.vue b/packages/nc-gui/components/shared-view/Kanban.vue index ab88c584bf..c92d9bc9f2 100644 --- a/packages/nc-gui/components/shared-view/Kanban.vue +++ b/packages/nc-gui/components/shared-view/Kanban.vue @@ -27,6 +27,8 @@ provide(IsPublicInj, ref(true)) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) +useProvideSmartsheetLtarHelpers(meta) + useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideKanbanViewStore(meta, sharedView, true) diff --git a/packages/nc-gui/components/shared-view/Map.vue b/packages/nc-gui/components/shared-view/Map.vue index ccadbc2452..2e64490fce 100644 --- a/packages/nc-gui/components/shared-view/Map.vue +++ b/packages/nc-gui/components/shared-view/Map.vue @@ -27,6 +27,8 @@ provide(IsPublicInj, ref(true)) useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true) +useProvideSmartsheetLtarHelpers(meta) + useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters) useProvideMapViewStore(meta, sharedView, true) diff --git a/packages/nc-gui/components/smartsheet/Cell.vue b/packages/nc-gui/components/smartsheet/Cell.vue index 23e6de92db..fb8fb8d7c4 100644 --- a/packages/nc-gui/components/smartsheet/Cell.vue +++ b/packages/nc-gui/components/smartsheet/Cell.vue @@ -154,43 +154,10 @@ const onContextmenu = (e: MouseEvent) => { e.stopPropagation() } } - -// Todo: move intersection logic to a separate component or a vue directive -const intersected = ref(false) - -const intersectionObserver = ref() - -const elementToObserve = ref() - -// load the cell only when it is in the viewport -function initIntersectionObserver() { - intersectionObserver.value = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - // if the cell is in the viewport, load the cell and disconnect the observer - if (entry.isIntersecting) { - intersected.value = true - intersectionObserver.value?.disconnect() - intersectionObserver.value = undefined - } - }) - }) -} - -// observe the cell when it is mounted -onMounted(() => { - initIntersectionObserver() - intersectionObserver.value?.observe(elementToObserve.value!) -}) - -// disconnect the observer when the cell is unmounted -onUnmounted(() => { - intersectionObserver.value?.disconnect() -}) diff --git a/packages/nc-gui/components/smartsheet/Form.vue b/packages/nc-gui/components/smartsheet/Form.vue index f5d7b07245..d02c189500 100644 --- a/packages/nc-gui/components/smartsheet/Form.vue +++ b/packages/nc-gui/components/smartsheet/Form.vue @@ -120,7 +120,6 @@ reloadEventHook.on(async () => { const { fields, showAll, hideAll } = useViewColumnsOrThrow() const { state, row } = useProvideSmartsheetRowStore( - meta, ref({ row: formState.value, oldRow: {}, diff --git a/packages/nc-gui/components/smartsheet/Row.vue b/packages/nc-gui/components/smartsheet/Row.vue index 4b5567e90e..502c14e5fc 100644 --- a/packages/nc-gui/components/smartsheet/Row.vue +++ b/packages/nc-gui/components/smartsheet/Row.vue @@ -1,6 +1,4 @@