diff --git a/cypress/e2e/tables-import.cy.js b/cypress/e2e/tables-import.cy.js
index e29299690..81b9dd00c 100644
--- a/cypress/e2e/tables-import.cy.js
+++ b/cypress/e2e/tables-import.cy.js
@@ -20,13 +20,15 @@ describe('Import csv', () => {
cy.get('.modal__content button').contains('Select from Files').click()
cy.get('.file-picker__files').contains('test-import').click()
cy.get('.file-picker button span').contains('Choose test-import.csv').click()
+ cy.get('.modal__content .import-filename', { timeout: 5000 }).should('be.visible')
cy.get('.modal__content button').contains('Import').click()
+
cy.get('[data-cy="importResultColumnsFound"]').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('not.exist')
- cy.get('[data-cy="importResultRowErrors"]').should('not.exist')
+ cy.get('[data-cy="importResultParsingErrors"]').should('contain.text', '0')
+ cy.get('[data-cy="importResultRowErrors"]').should('contain.text', '0')
})
it('Import csv from device', () => {
@@ -35,12 +37,67 @@ describe('Import csv', () => {
cy.get('.modal__content button').contains('Upload from device').click()
cy.get('input[type="file"]').selectFile('cypress/fixtures/test-import.csv', { force: true })
cy.get('.modal__content button').contains('Import').click()
- cy.get('[data-cy="importResultColumnsFound"]').should('contain.text', '4')
+
+ 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('not.exist')
- cy.get('[data-cy="importResultRowErrors"]').should('not.exist')
+ 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', () => {
+
+ before(function() {
+ cy.createRandomUser().then(user => {
+ localUser = user
+ })
+ })
+
+ beforeEach(function() {
+ cy.login(localUser)
+ cy.visit('apps/files/files')
+ cy.uploadFile('test-import.csv', 'text/csv')
+ })
+
+ it('Import to new table', () => {
+ cy.reload()
+
+ cy.get('[data-cy-files-list-row-actions] .action-item button').click()
+ cy.get('[data-cy-files-list-row-action="import-to-tables"]').click()
+
+ cy.intercept({ method: 'POST', url: '**/apps/tables/import/table/*'}).as('importNewTableReq')
+ cy.get('[data-cy="fileActionImportButton"]').click()
+ cy.wait('@importNewTableReq').its('response.statusCode').should('equal', 200)
+
+ cy.get('[data-cy="importResultColumnsFound"]').should('contain.text', '4')
+ cy.get('[data-cy="importResultColumnsMatch"]').should('contain.text', '0')
+ cy.get('[data-cy="importResultColumnsCreated"]').should('contain.text', '4')
+ 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')
})
+ it('Import to existing table', () => {
+ cy.get('[data-cy-files-list-row-actions] .action-item button').click()
+ cy.get('[data-cy-files-list-row-action="import-to-tables"]').click()
+
+ cy.get('[data-cy="importAsNewTableSwitch"]').click()
+ cy.get('[data-cy="selectExistingTableDropdown"]').type('tutorial')
+ cy.get('.name-parts').click()
+
+ cy.intercept({ method: 'POST', url: '**/apps/tables/import/table/*'}).as('importExistingTableReq')
+ cy.get('[data-cy="fileActionImportButton"]').click()
+ cy.wait('@importExistingTableReq').its('response.statusCode').should('equal', 200)
+
+ cy.get('[data-cy="importResultColumnsFound"]').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')
+ })
+
})
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 05047f29a..738534a66 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -6,6 +6,7 @@
use OCA\Analytics\Datasource\DatasourceEvent;
use OCA\Tables\Capabilities;
use OCA\Tables\Listener\AnalyticsDatasourceListener;
+use OCA\Tables\Listener\LoadAdditionalListener;
use OCA\Tables\Listener\TablesReferenceListener;
use OCA\Tables\Listener\UserDeletedListener;
use OCA\Tables\Reference\ContentReferenceProvider;
@@ -17,10 +18,12 @@
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Reference\RenderReferenceEvent;
+use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
use OCP\IConfig;
use OCP\Server;
use OCP\User\Events\BeforeUserDeletedEvent;
use Psr\Container\ContainerExceptionInterface;
+
use Psr\Container\NotFoundExceptionInterface;
class Application extends App implements IBootstrap {
@@ -45,6 +48,8 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class);
$context->registerEventListener(RenderReferenceEvent::class, TablesReferenceListener::class);
+ $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
+
$context->registerSearchProvider(SearchTablesProvider::class);
try {
diff --git a/lib/Listener/LoadAdditionalListener.php b/lib/Listener/LoadAdditionalListener.php
new file mode 100644
index 000000000..6978f047b
--- /dev/null
+++ b/lib/Listener/LoadAdditionalListener.php
@@ -0,0 +1,20 @@
+ */
+class LoadAdditionalListener implements IEventListener {
+ public function handle(Event $event): void {
+ if (!($event instanceof LoadAdditionalScriptsEvent)) {
+ return;
+ }
+
+ Util::addScript(Application::APP_ID, 'tables-files', 'files');
+ }
+}
diff --git a/src/file-actions.js b/src/file-actions.js
new file mode 100644
index 000000000..50dd896d0
--- /dev/null
+++ b/src/file-actions.js
@@ -0,0 +1,33 @@
+import { FileAction, registerFileAction } from '@nextcloud/files'
+import { spawnDialog } from '@nextcloud/dialogs'
+import tablesIcon from '@mdi/svg/svg/table-large.svg?raw'
+
+__webpack_nonce__ = btoa(OC.requestToken) // eslint-disable-line
+__webpack_public_path__ = OC.linkTo('tables', 'js/') // eslint-disable-line
+
+const validMimeTypes = [
+ 'text/csv',
+ 'text/html',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.ms-excel',
+]
+
+const fileAction = new FileAction({
+ id: 'import-to-tables',
+ displayName: () => t('tables', 'Import into Tables'),
+ iconSvgInline: () => tablesIcon,
+
+ enabled: (files) => {
+ const file = files[0]
+
+ return file.type === 'file' && validMimeTypes.includes(file.mime)
+ },
+
+ exec: async (file) => {
+ const { default: FileActionImport } = await import('./modules/modals/FileActionImport.vue')
+ spawnDialog(FileActionImport, { file })
+ return null
+ },
+})
+
+registerFileAction(fileAction)
diff --git a/src/modules/modals/FileActionImport.vue b/src/modules/modals/FileActionImport.vue
new file mode 100644
index 000000000..072925014
--- /dev/null
+++ b/src/modules/modals/FileActionImport.vue
@@ -0,0 +1,281 @@
+
+ {{ t('tables', 'Import file into Tables') }}
+
+
{{ t('tables', 'Found columns') }} | ++ {{ results.found_columns_count }} + | +
{{ t('tables', 'Matching columns') }} | ++ {{ results.matching_columns_count }} + | +
{{ t('tables', 'Created columns') }} | ++ {{ results.created_columns_count }} + | +
{{ t('tables', 'Inserted rows') }} | ++ {{ results.inserted_rows_count }} + | +
{{ t('tables', 'Value parsing errors') }} | ++ {{ results.errors_parsing_count }} + | +
{{ t('tables', 'Row creation errors') }} | ++ {{ results.errors_count }} + | +