From 20cdc2c3f0f40d592445df90f8a2c600decbda16 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Fri, 10 Jan 2025 17:24:17 +0100 Subject: [PATCH] feat(Templates): extend the tutorial table Signed-off-by: Arthur Schiwon --- cypress/e2e/tables-archive.cy.js | 8 ++-- cypress/e2e/tables-export-csv.cy.js | 12 +++-- cypress/e2e/tables-favorite.cy.js | 19 ++++---- cypress/e2e/tables-fe-filters.cy.js | 2 +- cypress/e2e/tables-import.cy.js | 6 +-- cypress/e2e/tables-rows.cy.js | 6 +-- cypress/support/commands.js | 6 +++ lib/Service/TableService.php | 69 ++++++++-------------------- lib/Service/TableTemplateService.php | 69 +++++++++++++++++----------- 9 files changed, 94 insertions(+), 103 deletions(-) diff --git a/cypress/e2e/tables-archive.cy.js b/cypress/e2e/tables-archive.cy.js index 01622385d..7fb16cfa3 100644 --- a/cypress/e2e/tables-archive.cy.js +++ b/cypress/e2e/tables-archive.cy.js @@ -21,8 +21,8 @@ describe('Archive tables/views', () => { it('can archive tables', () => { cy.get('[data-cy="navigationTableItem"]').first().as('tutorialTable') - cy.get('@tutorialTable').should('contain.text', 'Tutorial') - cy.get('@tutorialTable').find('[aria-haspopup="menu"]').click({ force: true }) + cy.get('@tutorialTable').should('contain.text', 'Welcome to Nextcloud Tables!') + cy.get('@tutorialTable').find('[aria-haspopup="menu"]').first().click({ force: true }) cy.intercept({ method: 'PUT', url: '**/apps/tables/api/2/tables/*'}).as('archiveTableReq') cy.contains('Archive table').click({ force: true }) @@ -38,8 +38,8 @@ describe('Archive tables/views', () => { it('can unarchive tables', () => { cy.get('[data-cy="navigationTableItem"]').first().as('tutorialTable') - cy.get('@tutorialTable').should('contain.text', 'Tutorial') - cy.get('@tutorialTable').find('[aria-haspopup="menu"]').click({ force: true }) + cy.get('@tutorialTable').should('contain.text', 'Welcome to Nextcloud Tables!') + cy.get('@tutorialTable').find('[aria-haspopup="menu"]').first().click({ force: true }) cy.intercept({ method: 'PUT', url: '**/apps/tables/api/2/tables/*' }).as('unarchiveTableReq') cy.contains('Unarchive table').click({ force: true }) diff --git a/cypress/e2e/tables-export-csv.cy.js b/cypress/e2e/tables-export-csv.cy.js index f76eea6bb..909a7f1b4 100644 --- a/cypress/e2e/tables-export-csv.cy.js +++ b/cypress/e2e/tables-export-csv.cy.js @@ -18,15 +18,17 @@ describe('Import csv', () => { }) it('Export csv', () => { - cy.loadTable('Tutorial') + cy.loadTable('Welcome to Nextcloud Tables!') cy.clickOnTableThreeDotMenu('Export as CSV') const hour = new Date().getHours().toString().length < 2 ? '0' + new Date().getHours() : new Date().getHours().toString() const minutes = new Date().getMinutes().toString().length < 2 ? '0' + new Date().getMinutes() : new Date().getMinutes().toString() const date = new Date().toISOString().slice(2, 10) - const fileName = date + '_' + hour + '-' + minutes + '_' + 'Tutorial.csv' - cy.log('filename: ' + fileName) - cy.readFile('cypress/downloads/' + fileName).should('contain', 'What,How to do,Ease of use,Done') - cy.readFile('cypress/downloads/' + fileName).should('contain', 'Open the tables app,Click on tables icon in the menu bar.,5,true') + cy.getTutorialTableName().then(tutorialName => { + const fileName = date + '_' + hour + '-' + minutes + '_' + tutorialName + '.csv' + cy.log('filename: ' + fileName) + cy.readFile('cypress/downloads/' + fileName).should('contain', 'What,How to do,Ease of use,Done') + cy.readFile('cypress/downloads/' + fileName).should('contain', 'Open the tables app,Reachable via the Tables icon in the apps list.,5,true') + }) }) }) diff --git a/cypress/e2e/tables-favorite.cy.js b/cypress/e2e/tables-favorite.cy.js index 73d4d295a..ccc9b853d 100644 --- a/cypress/e2e/tables-favorite.cy.js +++ b/cypress/e2e/tables-favorite.cy.js @@ -21,8 +21,8 @@ describe('Favorite tables/views', () => { it('can favorite tables', () => { cy.get('[data-cy="navigationTableItem"]').first().as('tutorialTable') - cy.get('@tutorialTable').should('contain.text', 'Tutorial') - cy.get('@tutorialTable').find('[aria-haspopup="menu"]').click({ force: true }) + cy.get('@tutorialTable').should('contain.text', 'Welcome to Nextcloud Tables!') + cy.get('@tutorialTable').find('[aria-haspopup="menu"]').first().click({ force: true }) cy.intercept({ method: 'POST', url: '**/ocs/v2.php/apps/tables/api/2/favorites/*/*'}).as('favoriteTableReq') cy.contains('Add to favorites').click({ force: true }) @@ -34,8 +34,8 @@ describe('Favorite tables/views', () => { it('can remove favorite table', () => { cy.get('[data-cy="navigationTableItem"]').first().as('tutorialTable') - cy.get('@tutorialTable').should('contain.text', 'Tutorial') - cy.get('@tutorialTable').find('[aria-haspopup="menu"]').click({ force: true }) + cy.get('@tutorialTable').should('contain.text', 'Welcome to Nextcloud Tables!') + cy.get('@tutorialTable').find('[aria-haspopup="menu"]').first().click({ force: true }) cy.intercept({ method: 'DELETE', url: '**/ocs/v2.php/apps/tables/api/2/favorites/*/*' }).as('unfavoriteTableReq') cy.contains('Remove from favorites').click({ force: true }) @@ -45,12 +45,11 @@ describe('Favorite tables/views', () => { }) it('can favorite views', () => { - cy.loadTable('Tutorial') - cy.createView('test') + cy.loadTable('Welcome to Nextcloud Tables!') cy.get('[data-cy="navigationViewItem"]').first().as('testView') - cy.get('@testView').parent().parent().parent().should('contain.text', 'Tutorial') + cy.get('@testView').parent().parent().parent().should('contain.text', 'Welcome to ') cy.get('@testView').find('[aria-haspopup="menu"]').click({ force: true }) cy.intercept({ method: 'POST', url: '**/ocs/v2.php/apps/tables/api/2/favorites/*/*' }).as('favoriteViewReq') @@ -70,20 +69,20 @@ describe('Favorite tables/views', () => { cy.contains('Remove from favorites').click({ force: true }) cy.wait('@unfavoriteViewReq').its('response.statusCode').should('equal', 200) - cy.get('@testView').parent().parent().parent().should('contain.text', 'Tutorial') + cy.get('@testView').parent().parent().parent().should('contain.text', 'Welcome to ') }) it('can (un)favorite views with favorited parent tables', () => { cy.get('[data-cy="navigationViewItem"]').first().as('testView') cy.get('[data-cy="navigationTableItem"]').first().as('tutorialTable') - cy.get('@testView').parent().parent().parent().should('contain.text', 'Tutorial') + cy.get('@testView').parent().parent().parent().should('contain.text', 'Welcome to ') cy.get('@testView').find('[aria-haspopup="menu"]').click({ force: true }) cy.contains('Add to favorites').click({ force: true }) cy.get('@testView').parent().should('contain.text', 'Favorites') - cy.get('@tutorialTable').should('contain.text', 'Tutorial') + cy.get('@tutorialTable').should('contain.text', 'Welcome to Nextcloud Tables!') cy.get('@tutorialTable').find('[aria-haspopup="menu"]').first().click({ force: true }) cy.contains('Add to favorites').click({ force: true }) diff --git a/cypress/e2e/tables-fe-filters.cy.js b/cypress/e2e/tables-fe-filters.cy.js index cf65bac3b..a90da5cb3 100644 --- a/cypress/e2e/tables-fe-filters.cy.js +++ b/cypress/e2e/tables-fe-filters.cy.js @@ -18,7 +18,7 @@ describe('FE sorting and filtering', () => { }) it('FE Search in table', () => { - cy.get('.app-navigation-entry-link').contains('Tutorial').click({ force: true }) + cy.get('.app-navigation-entry-link').contains('Welcome to Nextcloud Tables!').click({ force: true }) // test case-sensitive cy.contains('Edit a row').should('exist') diff --git a/cypress/e2e/tables-import.cy.js b/cypress/e2e/tables-import.cy.js index 127d136d5..c7ac618b2 100644 --- a/cypress/e2e/tables-import.cy.js +++ b/cypress/e2e/tables-import.cy.js @@ -20,7 +20,7 @@ describe('Import csv', () => { }) it('Import csv from Files', () => { - cy.loadTable('Tutorial') + cy.loadTable('Welcome to Nextcloud Tables!') cy.clickOnTableThreeDotMenu('Import') cy.get('.modal__content button').contains('Select from Files').click() cy.get('.file-picker__files').contains('test-import').click() @@ -42,7 +42,7 @@ describe('Import csv', () => { }) it('Import csv from device', () => { - cy.loadTable('Tutorial') + 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.csv', { force: true }) @@ -102,7 +102,7 @@ describe('Import csv from Files file action', () => { 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('[data-cy="selectExistingTableDropdown"]').type('Welcome to Nextcloud Tables!') cy.get('.name-parts').click() cy.intercept({ diff --git a/cypress/e2e/tables-rows.cy.js b/cypress/e2e/tables-rows.cy.js index e371cdf01..ea52db11c 100644 --- a/cypress/e2e/tables-rows.cy.js +++ b/cypress/e2e/tables-rows.cy.js @@ -18,7 +18,7 @@ describe('Rows for a table', () => { }) it('Create', () => { - cy.get('.app-navigation-entry-link').contains('Tutorial').click({ force: true }) + cy.get('.app-navigation-entry-link').contains('Welcome to Nextcloud Tables!').click({ force: true }) cy.get('.NcTable').contains('Create row').click({ force: true }) cy.get('.modal__content .slot input').first().type('My first task') cy.get('.modal__content .ProseMirror').first().click() @@ -32,7 +32,7 @@ describe('Rows for a table', () => { }) it('Edit', () => { - cy.get('.app-navigation-entry-link').contains('Tutorial').click({ force: true }) + cy.get('.app-navigation-entry-link').contains('Welcome to Nextcloud Tables!').click({ force: true }) cy.get('.custom-table table').contains('My first task').parent().parent().find('[aria-label="Edit row"]').click() cy.get('.modal__content .slot input').first().clear().type('Changed column value') cy.get('.modal__content [aria-label="Increase stars"]').click().click() @@ -43,7 +43,7 @@ describe('Rows for a table', () => { }) it('Delete', () => { - cy.get('.app-navigation-entry-link').contains('Tutorial').click({ force: true }) + cy.get('.app-navigation-entry-link').contains('Welcome to Nextcloud Tables!').click({ force: true }) cy.get('.custom-table table').contains('Changed column value').parent().parent().find('[aria-label="Edit row"]').click() cy.get('.modal-container button').contains('Delete').click() cy.get('.modal-container button').contains('I really want to delete this row!').click() diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 6109a0e87..13a02095d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -145,6 +145,12 @@ Cypress.Commands.add('loadTable', (name) => { cy.get('[data-cy="navigationTableItem"] a[title="' + name + '"]').click({ force: true }) }) +Cypress.Commands.add('getTutorialTableName', () => { + return cy.get('[data-cy="navigationTableItem"] a[title^="Welcome to"]').invoke('attr', 'title').then((title) => { + return cy.wrap(title) + }) +}) + Cypress.Commands.add('loadView', (name) => { cy.get('[data-cy="navigationViewItem"] a[title="' + name + '"]').click({ force: true }) }) diff --git a/lib/Service/TableService.php b/lib/Service/TableService.php index 77d80ed79..ba70c9e7e 100644 --- a/lib/Service/TableService.php +++ b/lib/Service/TableService.php @@ -25,6 +25,7 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\Exception as OcpDbException; +use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -33,59 +34,26 @@ * @psalm-import-type TablesTable from ResponseDefinitions */ class TableService extends SuperService { - private TableMapper $mapper; - - private TableTemplateService $tableTemplateService; - - private ColumnService $columnService; - - private RowService $rowService; - - private ViewService $viewService; - - private ShareService $shareService; - - protected UserHelper $userHelper; - - protected FavoritesService $favoritesService; - - protected IAppManager $appManager; - - protected IL10N $l; - private ContextService $contextService; - - protected IEventDispatcher $eventDispatcher; public function __construct( - PermissionsService $permissionsService, - LoggerInterface $logger, - ?string $userId, - TableMapper $mapper, - TableTemplateService $tableTemplateService, - ColumnService $columnService, - RowService $rowService, - ViewService $viewService, - ShareService $shareService, - UserHelper $userHelper, - FavoritesService $favoritesService, - IEventDispatcher $eventDispatcher, - ContextService $contextService, - IAppManager $appManager, - IL10N $l, + PermissionsService $permissionsService, + LoggerInterface $logger, + ?string $userId, + private TableMapper $mapper, + private TableTemplateService $tableTemplateService, + private ColumnService $columnService, + private RowService $rowService, + private ViewService $viewService, + private ShareService $shareService, + protected UserHelper $userHelper, + protected FavoritesService $favoritesService, + protected IEventDispatcher $eventDispatcher, + private ContextService $contextService, + protected IAppManager $appManager, + protected IL10N $l, + protected Defaults $themingDefaults, ) { parent::__construct($logger, $userId, $permissionsService); - $this->appManager = $appManager; - $this->mapper = $mapper; - $this->tableTemplateService = $tableTemplateService; - $this->columnService = $columnService; - $this->rowService = $rowService; - $this->viewService = $viewService; - $this->shareService = $shareService; - $this->userHelper = $userHelper; - $this->favoritesService = $favoritesService; - $this->l = $l; - $this->eventDispatcher = $eventDispatcher; - $this->contextService = $contextService; } /** @@ -119,7 +87,8 @@ public function findAll(?string $userId = null, bool $skipTableEnhancement = fal // if there are no own tables found, create the tutorial table if (count($allTables) === 0 && $createTutorial) { try { - $tutorialTable = $this->create($this->l->t('Tutorial'), 'tutorial', '🚀'); + $productName = $this->themingDefaults->getName(); + $tutorialTable = $this->create($this->l->t('Welcome to %s Tables!', [$productName]), 'tutorial', '🚀'); $allTables[$tutorialTable->getId()] = $tutorialTable; } catch (InternalError|PermissionError|DoesNotExistException|MultipleObjectsReturnedException|OcpDbException $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); diff --git a/lib/Service/TableTemplateService.php b/lib/Service/TableTemplateService.php index 8237d1a1d..794ca0a48 100644 --- a/lib/Service/TableTemplateService.php +++ b/lib/Service/TableTemplateService.php @@ -16,31 +16,23 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\DB\Exception; +use OCP\Defaults; use OCP\IL10N; use Psr\Log\LoggerInterface; class TableTemplateService { - private IL10N $l; - - private ColumnService $columnService; - - private RowService $rowService; - - private ViewService $viewService; - - protected LoggerInterface $logger; - - private ?string $userId; private string $textRichColumnTypeName = 'rich'; - public function __construct(LoggerInterface $logger, IL10N $l, ColumnService $columnService, ?string $userId, RowService $rowService, ViewService $viewService) { - $this->logger = $logger; - $this->l = $l; - $this->columnService = $columnService; - $this->rowService = $rowService; - $this->viewService = $viewService; - $this->userId = $userId; + public function __construct( + protected LoggerInterface $logger, + private IL10N $l, + private ColumnService $columnService, + private ?string $userId, + private RowService $rowService, + private ViewService $viewService, + protected Defaults $themingDefaults, + ) { } /** @@ -679,7 +671,7 @@ private function makeTodo(Table $table): void { // TRANSLATORS This is an example target $columns['target']->getId() => $this->l->t('We know what we are doing.'), // TRANSLATORS This is an example comment - $columns['comments']->getId() => $this->l->t('We have heard that Nextcloud could be a nice solution for it, should give it a try.'), + $columns['comments']->getId() => $this->l->t('We have heard that %s could be a nice solution for it, should give it a try.', [$this->themingDefaults->getName()]), $columns['progress']->getId() => 10, $columns['proofed']->getId() => 'false', ]); @@ -744,42 +736,65 @@ private function makeStartupTable(Table $table):void { // TRANSLATORS This is an example name $columns['what']->getId() => $this->l->t('Open the tables app'), // TRANSLATORS This is an example account manager - $columns['how']->getId() => 'Click on tables icon in the menu bar.', + $columns['how']->getId() => $this->l->t('Reachable via the Tables icon in the apps list.'), $columns['ease']->getId() => 5, $columns['done']->getId() => 'true', ]); $this->createRow($table, [ // TRANSLATORS This is an example name $columns['what']->getId() => $this->l->t('Add your first row'), - // TRANSLATORS This is an example account manager - $columns['how']->getId() => 'Just click on "new row" and enter some data inside of the form. At the end click on the bottom "save".', + // TRANSLATORS: the asterisks (*) are used to format the button name in italics + $columns['how']->getId() => $this->l->t('Use the *+ Create row* button and enter some data inside of the form.'), $columns['ease']->getId() => 5, $columns['done']->getId() => 'false', ]); $this->createRow($table, [ // TRANSLATORS This is an example name $columns['what']->getId() => $this->l->t('Edit a row'), - // TRANSLATORS This is an example account manager - $columns['how']->getId() => 'Hover the mouse over a row you want to edit. Click on the pen on the right side. Maybe you want to add a "done" status to this row.', + // TRANSLATORS: the asterisks (*) are used to format the icon name and column name in italics + $columns['how']->getId() => $this->l->t('Go to a row you want to edit and use the *pencil* edit button. Maybe you want to add a *Done* status to this row?'), $columns['ease']->getId() => 5, $columns['done']->getId() => 'false', ]); $this->createRow($table, [ // TRANSLATORS This is an example name $columns['what']->getId() => $this->l->t('Add a new column'), + // TRANSLATORS: the asterisks (*) are used to format the menu entry title in italics + $columns['how']->getId() => $this->l->t('You can add, remove and adjust columns as you need. Open the three-dot-menu on the upper right of this table and choose *Create column*. Fill in the data you want, at least a title and column type.'), + $columns['ease']->getId() => 4, + $columns['done']->getId() => 'false', + ]); + $this->createRow($table, [ + // TRANSLATORS This is an example name + $columns['what']->getId() => $this->l->t('Create views for tables'), // TRANSLATORS This is an example account manager - $columns['how']->getId() => 'You can add, remove and adjust columns as you need it. Click on the three-dot-menu on the upper right of this table and choose "create column". Fill in the data you want, at least a title and column type.', + $columns['how']->getId() => $this->l->t('Filter data and save table presets as views to share and combine them into applications.'), $columns['ease']->getId() => 4, $columns['done']->getId() => 'false', ]); $this->createRow($table, [ // TRANSLATORS This is an example name - $columns['what']->getId() => $this->l->t('Read the docs'), + $columns['what']->getId() => $this->l->t('Create applications'), // TRANSLATORS This is an example account manager - $columns['how']->getId() => 'If you want to go through the documentation, this can be found here: https://github.com/nextcloud/tables/wiki', + $columns['how']->getId() => $this->l->t('Combine different tables and views into no-code applications for any purpose. This makes them easily accessible directly in the app bar.'), + $columns['ease']->getId() => 3, + $columns['done']->getId() => 'false', + ]); + $this->createRow($table, [ + // TRANSLATORS This is an example name + $columns['what']->getId() => $this->l->t('Read the docs'), + // TRANSLATORS: the link is formatted using markdown, the pattern is: [Title](URL) + $columns['how']->getId() => $this->l->t('If you want to go through the documentation, this can be found here: [Nextcloud Tables documentation](%s)', ['https://github.com/nextcloud/tables/wiki']), $columns['ease']->getId() => 3, $columns['done']->getId() => 'false', ]); + + $this->createView($table, [ + 'title' => $this->l->t('Check yourself!'), + 'emoji' => '🏁', + 'columns' => json_encode([$columns['what']->getId(), $columns['how']->getId(), $columns['ease']->getId(), $columns['done']->getId()]), + 'filter' => json_encode([[['columnId' => $columns['done']->getId(), 'operator' => 'is-equal', 'value' => '@unchecked']]]), + ]); } /**