Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions cypress/e2e/tables-import.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
103 changes: 60 additions & 43 deletions lib/Service/ImportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -750,57 +744,74 @@ 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) {
} 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;
Expand All @@ -817,11 +828,17 @@ 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) {
$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;
$this->countCreatedColumns = $countCreatedColumnsFromConfig;
Expand Down
Loading