From 99af186226421b05cc26e12486e4d85fbaed9c2d Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sat, 8 Aug 2026 23:24:48 +0200 Subject: [PATCH 1/3] Fix: Fix column ignorance on import Signed-off-by: Kostiantyn Miakshyn --- lib/Service/ImportService.php | 100 ++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index d8adc4b521..bc0ee3fc04 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -205,7 +205,7 @@ private function getPreviewData(Worksheet $worksheet): array { $this->getColumns($firstRow, $secondRow); foreach ($this->rawColumnTitles as $colIndex => $title) { - if ($this->columns[$colIndex] !== '') { + if (isset($this->columns[$colIndex]) && $this->columns[$colIndex] !== '') { /** @var Column $column */ $column = $this->columns[$colIndex]; $columns[] = $column; @@ -623,20 +623,14 @@ private function upsertRow(Row $row, array $columnBusinesses): void { continue; } - $columnKey = $i; - if ($this->columnsConfig && $this->idColumnIndex !== null && $i > $this->idColumnIndex) { - // if we have an ID column, we need to adjust the index - $columnKey = $i - 1; - } - // only add the dataset if column is known - if (!isset($this->columns[$columnKey]) || $this->columns[$columnKey] === '') { + if (!isset($this->columns[$i]) || $this->columns[$i] === '') { $this->logger->debug('Column unknown while fetching rows data for importing.'); continue; } /** @var Column $column */ - $column = $this->columns[$columnKey]; + $column = $this->columns[$i]; // if cell is empty if (!$cell || $cell->getValue() === null) { @@ -750,57 +744,76 @@ private function getColumns(Row $firstRow, Row $secondRow): void { $secondRowCellIterator = $secondRow->getCellIterator(); $titles = []; $dataTypes = []; + $rawColumnTitles = []; + $rawColumnDataTypes = []; + $columnFileIndices = []; $index = 0; $countMatchingColumnsFromConfig = 0; $countCreatedColumnsFromConfig = 0; $lastCellWasEmpty = false; $hasGapInTitles = false; + $this->columns = []; + foreach ($cellIterator as $cell) { if ($cell && $cell->getValue() !== null && $cell->getValue() !== '') { $title = $cell->getValue(); + $titleRaw = $title; + $dataType = $this->parseColumnDataType($secondRowCellIterator->current()); + $shouldImport = true; if (!$this->columnsConfig && mb_strtolower($title) === Column::META_ID_TITLE) { $this->idColumnIndex = $index; - $titles[] = $title; - $dataTypes[] = $this->parseColumnDataType($secondRowCellIterator->current()); - $secondRowCellIterator->next(); - $index++; - continue; - } - if (isset($this->columnsConfig[$index]) && $this->columnsConfig[$index]['action'] === 'exist' && $this->columnsConfig[$index]['existColumn']) { - $title = $this->columnsConfig[$index]['existColumn']['label']; - $countMatchingColumnsFromConfig++; - - // no need to create the ID (Meta) column as it used for update - if ($this->columnsConfig[$index]['existColumn']['id'] === Column::TYPE_META_ID) { + $this->countMatchingColumns++; + $shouldImport = false; + } elseif (isset($this->columnsConfig[$index])) { + if ($this->columnsConfig[$index]['action'] === 'ignore') { + $shouldImport = false; + } elseif ( + $this->columnsConfig[$index]['action'] === 'exist' + && isset($this->columnsConfig[$index]['existColumn']) + && $this->columnsConfig[$index]['existColumn']['id'] === Column::TYPE_META_ID + ) { $this->idColumnIndex = $index; - $secondRowCellIterator->next(); - $index++; - continue; + $countMatchingColumnsFromConfig++; + $shouldImport = false; + } elseif ( + $this->columnsConfig[$index]['action'] === 'exist' + && $this->columnsConfig[$index]['existColumn'] + ) { + $title = $this->columnsConfig[$index]['existColumn']['label']; + $countMatchingColumnsFromConfig++; + } elseif ( + $this->columnsConfig[$index]['action'] === 'new' + && $this->createUnknownColumns + ) { + $column = $this->columnService->create( + $this->userId, + $this->tableId, + $this->viewId, + ColumnDto::createFromArray($this->columnsConfig[$index]), + $this->columnsConfig[$index]['selectedViewIds'] ?? [] + ); + $title = $column->getTitle(); + $countCreatedColumnsFromConfig++; } } - if (isset($this->columnsConfig[$index]) && $this->columnsConfig[$index]['action'] === 'new' && $this->createUnknownColumns) { - $column = $this->columnService->create( - $this->userId, - $this->tableId, - $this->viewId, - ColumnDto::createFromArray($this->columnsConfig[$index]), - $this->columnsConfig[$index]['selectedViewIds'] ?? [] - ); - $title = $column->getTitle(); - $countCreatedColumnsFromConfig++; + + $rawColumnTitles[] = $titleRaw; + $rawColumnDataTypes[] = $dataType; + + if ($shouldImport) { + $titles[] = $title; + $dataTypes[] = $dataType; + $columnFileIndices[] = $index; } - $titles[] = $title; - // Convert data type to our data type - $dataTypes[] = $this->parseColumnDataType($secondRowCellIterator->current()); if ($lastCellWasEmpty) { $hasGapInTitles = true; } $lastCellWasEmpty = false; } else { $this->logger->debug('No cell given or cellValue is empty while loading columns for importing'); - if ($cell->getDataType() === 'null') { + if ($cell && $cell->getDataType() === 'null') { // LibreOffice generated XLSX doc may have more empty columns in the first row. // Continue without increasing error count, but leave a marker to detect gaps in titles. $lastCellWasEmpty = true; @@ -817,11 +830,16 @@ private function getColumns(Row $firstRow, Row $secondRow): void { $this->countErrors++; } - $this->rawColumnTitles = $titles; - $this->rawColumnDataTypes = $dataTypes; + $this->rawColumnTitles = $rawColumnTitles; + $this->rawColumnDataTypes = $rawColumnDataTypes; try { - $this->columns = $this->columnService->findOrCreateColumnsByTitleForTableAsArray($this->tableId, $this->viewId, $titles, $dataTypes, $this->userId, $this->createUnknownColumns, $this->countCreatedColumns, $this->countMatchingColumns); + $result = $this->columnService->findOrCreateColumnsByTitleForTableAsArray($this->tableId, $this->viewId, $titles, $dataTypes, $this->userId, $this->createUnknownColumns, $this->countCreatedColumns, $this->countMatchingColumns); + foreach ($result as $resultIndex => $column) { + if ($column instanceof Column) { + $this->columns[$columnFileIndices[$resultIndex]] = $column; + } + } if (!empty($this->columnsConfig)) { $this->countMatchingColumns = $countMatchingColumnsFromConfig; $this->countCreatedColumns = $countCreatedColumnsFromConfig; From 2da4b59ddca94ddba833b7ff86070daaee29d6f4 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sat, 8 Aug 2026 23:27:26 +0200 Subject: [PATCH 2/3] Fix: Fix column ignorance on import (tests) Signed-off-by: Kostiantyn Miakshyn --- cypress/e2e/tables-import.cy.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cypress/e2e/tables-import.cy.js b/cypress/e2e/tables-import.cy.js index e9621399fe..4e87de151d 100644 --- a/cypress/e2e/tables-import.cy.js +++ b/cypress/e2e/tables-import.cy.js @@ -136,6 +136,38 @@ describe('Import csv', () => { cy.get('[data-cy="importResultRowErrors"]').should('contain.text', '0') }) + it('Import small csv from device with an extra ignored column', () => { + const csv = [ + ['What', 'How to do', 'Ease of use', 'Done', 'Extra column to ignore'], + ['A1', 'A2', '0', 'true', 'ignore1'], + ['B1', 'B2', '2', 'true', 'ignore2'], + ['C1', 'C2', '5', 'false', 'ignore3'], + ] + cy.writeFile('cypress/fixtures/test-import-with-extra.csv', csv.map(row => row.join(',')).join('\n')) + + cy.loadTable('Welcome to Nextcloud Tables!') + cy.clickOnTableThreeDotMenu('Import') + cy.get('.modal__content button').contains('Upload from device').click() + cy.get('input[type="file"]').selectFile('cypress/fixtures/test-import-with-extra.csv', { force: true }) + + cy.get('.modal__content input[type="checkbox"]').first().uncheck({ force: true }) + + cy.intercept({ method: 'POST', url: '**/apps/tables/importupload-preview/**' }).as('importPreviewUploadExtra') + cy.get('.modal__content button').contains('Preview').click() + cy.wait('@importPreviewUploadExtra') + cy.get('.file_import__preview tbody tr', { timeout: 20000 }).should('have.length', 5) + + cy.intercept({ method: 'POST', url: '**/apps/tables/v2/importupload/table/*' }).as('importUploadReqExtra') + cy.get('.modal__content button').contains('Import').click() + cy.wait('@importUploadReqExtra') + cy.get('[data-cy="importResultColumnsFound"]', { timeout: 20000 }).should('contain.text', '4') + cy.get('[data-cy="importResultColumnsMatch"]').should('contain.text', '4') + cy.get('[data-cy="importResultColumnsCreated"]').should('contain.text', '0') + cy.get('[data-cy="importResultRowsInserted"]').should('contain.text', '3') + cy.get('[data-cy="importResultParsingErrors"]').should('contain.text', '0') + cy.get('[data-cy="importResultRowErrors"]').should('contain.text', '0') + }) + }) describe('Import csv from Files file action', () => { From 885dcbc26a8bee45b32bacd43551a3a410b7adb7 Mon Sep 17 00:00:00 2001 From: Kostiantyn Miakshyn Date: Sun, 9 Aug 2026 00:15:54 +0200 Subject: [PATCH 3/3] Fix: Fix column ignorance on import Signed-off-by: Kostiantyn Miakshyn --- lib/Service/ImportService.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/Service/ImportService.php b/lib/Service/ImportService.php index bc0ee3fc04..42122ff45d 100644 --- a/lib/Service/ImportService.php +++ b/lib/Service/ImportService.php @@ -388,7 +388,7 @@ public function import(?int $tableId, ?int $viewId, string $path, bool $createMi } return [ - 'found_columns_count' => count($this->columns), + 'found_columns_count' => count(array_filter($this->columns, fn ($column): bool => $column instanceof Column)), 'matching_columns_count' => $this->countMatchingColumns, 'created_columns_count' => $this->countCreatedColumns, 'inserted_rows_count' => $this->countInsertedRows, @@ -527,7 +527,7 @@ public function importV2(string $userId, string $importType, string $path, ?int } return new ImportStats( - count($this->columns), + count(array_filter($this->columns, fn ($column): bool => $column instanceof Column)), $this->countMatchingColumns, $this->countCreatedColumns, $this->countInsertedRows, @@ -763,8 +763,6 @@ private function getColumns(Row $firstRow, Row $secondRow): void { if (!$this->columnsConfig && mb_strtolower($title) === Column::META_ID_TITLE) { $this->idColumnIndex = $index; - $this->countMatchingColumns++; - $shouldImport = false; } elseif (isset($this->columnsConfig[$index])) { if ($this->columnsConfig[$index]['action'] === 'ignore') { $shouldImport = false; @@ -836,9 +834,10 @@ private function getColumns(Row $firstRow, Row $secondRow): void { try { $result = $this->columnService->findOrCreateColumnsByTitleForTableAsArray($this->tableId, $this->viewId, $titles, $dataTypes, $this->userId, $this->createUnknownColumns, $this->countCreatedColumns, $this->countMatchingColumns); foreach ($result as $resultIndex => $column) { - if ($column instanceof Column) { - $this->columns[$columnFileIndices[$resultIndex]] = $column; - } + $this->columns[$columnFileIndices[$resultIndex]] = $column; + } + if ($this->idColumnIndex !== null && (!isset($this->columns[$this->idColumnIndex]) || !$this->columns[$this->idColumnIndex] instanceof Column)) { + $this->countMatchingColumns++; } if (!empty($this->columnsConfig)) { $this->countMatchingColumns = $countMatchingColumnsFromConfig;