diff --git a/cypress/e2e/view-filtering-selection.cy.js b/cypress/e2e/view-filtering-selection.cy.js new file mode 100644 index 000000000..4d5226ed6 --- /dev/null +++ b/cypress/e2e/view-filtering-selection.cy.js @@ -0,0 +1,371 @@ +let localUser + +describe('Filtering in a view by selection columns', () => { + + before(function() { + cy.createRandomUser().then(user => { + localUser = user + }) + }) + + beforeEach(function() { + cy.login(localUser) + cy.visit('apps/tables') + }) + + it('Setup table', () => { + cy.createTable('View filtering test table') + cy.createTextLineColumn('title', null, null, true) + cy.createSelectionColumn('selection', ['sel1', 'sel2', 'sel3', 'sel4'], null, false) + cy.createSelectionMultiColumn('multi selection', ['A', 'B', 'C', 'D'], null, false) + cy.createSelectionCheckColumn('check', null, false) + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'first row') + cy.fillInValueSelection('selection', 'sel1') + cy.fillInValueSelectionMulti('multi selection', ['A', 'B']) + cy.fillInValueSelectionCheck('check') + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'second row') + cy.fillInValueSelection('selection', 'sel2') + cy.fillInValueSelectionMulti('multi selection', ['B']) + cy.fillInValueSelectionCheck('check') + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'third row') + cy.fillInValueSelection('selection', 'sel3') + cy.fillInValueSelectionMulti('multi selection', ['C', 'B', 'D']) + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'fourth row') + cy.fillInValueSelectionMulti('multi selection', ['A']) + cy.fillInValueSelectionCheck('check') + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'fifths row') + cy.fillInValueSelection('selection', 'sel4') + cy.fillInValueSelectionMulti('multi selection', ['D']) + cy.fillInValueSelectionCheck('check') + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'sixths row') + cy.fillInValueSelection('selection', 'sel1') + cy.fillInValueSelectionMulti('multi selection', ['C', 'D']) + cy.fillInValueSelectionCheck('check') + cy.get('button').contains('Save').click() + + // add row + cy.get('button').contains('Create row').click() + cy.fillInValueTextLine('title', 'sevenths row') + cy.fillInValueSelection('selection', 'sel2') + cy.fillInValueSelectionMulti('multi selection', ['A', 'C', 'B', 'D']) + cy.get('button').contains('Save').click() + }) + + it('Filter view for single selection', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for single selection' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Is equal"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="sel2"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + let expected = ['sevenths row', 'second row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + let unexpected = ['first row', 'third row', 'fourth row', 'fifths row', 'sixths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + + // # change filter value + // ## adjust filter + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Edit view').click({ force: true }) + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="sel1"]').click() + + // ## update view + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Save View').click() + cy.wait('@updateView') + + // # check for expected rows + // ## expected + expected = ['first row', 'sixths row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + unexpected = ['second row', 'third row', 'fourth row', 'fifths row', 'sevenths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) + + it('Filter view for multi selection - equals', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for multi selection 1' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Is equal"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="A"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + const expected = ['fourth row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + const unexpected = ['first row', 'second row', 'third row', 'fifths row', 'sixths row', 'sevenths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) + + it('Filter view for multi selection - contains', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for multi selection 2' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="A"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + const expected = ['first row', 'fourth row', 'sevenths row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + const unexpected = ['second row', 'third row', 'fifths row', 'sixths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) + + it('Filter view for multi selection - multiple contains', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for multi selection 3' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="A"]').click() + + cy.get('button').contains('Add new filter').click() + cy.get('.modal-container .filter-group .v-select.select').eq(3).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(4).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(5).click() + cy.get('ul.vs__dropdown-menu li span[title="B"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + const expected = ['first row', 'sevenths row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + const unexpected = ['second row', 'third row', 'fourth row', 'fifths row', 'sixths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) + + it('Filter view for multi selection - multiple filter groups', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for multi selection 3' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="A"]').click() + + cy.get('button').contains('Add new filter').click() + cy.get('.modal-container .filter-group .v-select.select').eq(3).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(4).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(5).click() + cy.get('ul.vs__dropdown-menu li span[title="B"]').click() + + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(6).click() + cy.get('ul.vs__dropdown-menu li span[title="multi selection"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(7).click() + cy.get('ul.vs__dropdown-menu li span[title="Contains"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(8).click() + cy.get('ul.vs__dropdown-menu li span[title="D"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + const expected = ['first row', 'third row', 'fifths row', 'sixths row', 'sevenths row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + const unexpected = ['second row', 'fourths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) + + it('Filter view for selection check', () => { + cy.loadTable('View filtering test table') + + // # create view with filter + // ## create view and set title + const title = 'Filter for check selection' + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.v-popper__popper li button span').contains('Create view').click({ force: true }) + cy.get('.modal-container #settings-section_title input').type(title) + + // ## add filter + cy.get('button').contains('Add new filter group').click() + cy.get('.modal-container .filter-group .v-select.select').eq(0).click() + cy.get('ul.vs__dropdown-menu li span[title="check"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(1).click() + cy.get('ul.vs__dropdown-menu li span[title="Is equal"]').click() + cy.get('.modal-container .filter-group .v-select.select').eq(2).click() + cy.get('ul.vs__dropdown-menu li span[title="Checked"]').click() + + // ## save view + cy.intercept({ method: 'POST', url: '**/apps/tables/view' }).as('createView') + cy.intercept({ method: 'PUT', url: '**/apps/tables/view/*' }).as('updateView') + cy.contains('button', 'Create View').click() + cy.wait('@createView') + cy.wait('@updateView') + cy.contains('.app-navigation-entry-link span', title).should('exist') + + // # check for expected rows + // ## expected + const expected = ['first row', 'second row', 'fourth row', 'fifths row', 'sixths row'] + expected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('be.visible') + }) + + // ## not expected + const unexpected = ['third row', 'sevenths row'] + unexpected.forEach(item => { + cy.get('.custom-table table tr td div').contains(item).should('not.exist') + }) + }) +}) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 3d05189da..94b67ed02 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -422,3 +422,21 @@ Cypress.Commands.add('removeColumn', (title) => { cy.get('.v-popper__popper ul.nc-button-group-content').last().get('button').last().click() cy.get('.modal__content button').contains('Confirm').click() }) + +// fill in a value in the 'create row' or 'edit row' model +Cypress.Commands.add('fillInValueTextLine', (columnTitle, value) => { + cy.get('.modal__content [data-cy="' + columnTitle + '"] .slot input').type(value) +}) +Cypress.Commands.add('fillInValueSelection', (columnTitle, optionLabel) => { + cy.get('.modal__content [data-cy="' + columnTitle + '"] .slot input').click() + cy.get('ul.vs__dropdown-menu li span[title="' + optionLabel + '"]').click() +}) +Cypress.Commands.add('fillInValueSelectionMulti', (columnTitle, optionLabels) => { + optionLabels.forEach(item => { + cy.get('.modal__content [data-cy="' + columnTitle + '"] .slot input').click() + cy.get('ul.vs__dropdown-menu li span[title="' + item + '"]').click() + }) +}) +Cypress.Commands.add('fillInValueSelectionCheck', (columnTitle) => { + cy.get('.modal__content [data-cy="' + columnTitle + '"] .checkbox-radio-switch__label').click() +}) diff --git a/lib/Db/Row2Mapper.php b/lib/Db/Row2Mapper.php index 6dc3ab993..f8603ddee 100644 --- a/lib/Db/Row2Mapper.php +++ b/lib/Db/Row2Mapper.php @@ -311,8 +311,21 @@ private function getFilterExpression(IQueryBuilder $qb, Column $column, string $ case 'ends-with': return $qb2->andWhere($qb->expr()->like('value', $qb->createNamedParameter($this->db->escapeLikeParameter($value).'%', $paramType))); case 'contains': + if ($column->getType() === 'selection' && $column->getSubtype() === 'multi') { + $value = str_replace(['"', '\''], '', $value); + return $qb2->andWhere($qb2->expr()->orX( + $qb->expr()->like('value', $qb->createNamedParameter('['.$this->db->escapeLikeParameter($value).']')), + $qb->expr()->like('value', $qb->createNamedParameter('['.$this->db->escapeLikeParameter($value).',%')), + $qb->expr()->like('value', $qb->createNamedParameter('%,'.$this->db->escapeLikeParameter($value).']%')), + $qb->expr()->like('value', $qb->createNamedParameter('%,'.$this->db->escapeLikeParameter($value).',%')) + )); + } return $qb2->andWhere($qb->expr()->like('value', $qb->createNamedParameter('%'.$this->db->escapeLikeParameter($value).'%', $paramType))); case 'is-equal': + if ($column->getType() === 'selection' && $column->getSubtype() === 'multi') { + $value = str_replace(['"', '\''], '', $value); + return $qb2->andWhere($qb->expr()->eq('value', $qb->createNamedParameter('['.$this->db->escapeLikeParameter($value).']', $paramType))); + } return $qb2->andWhere($qb->expr()->eq('value', $qb->createNamedParameter($value, $paramType))); case 'is-greater-than': return $qb2->andWhere($qb->expr()->gt('value', $qb->createNamedParameter($value, $paramType))); @@ -366,7 +379,7 @@ private function getMetaFilterExpression(IQueryBuilder $qb, int $columnId, strin /** * @param string $operator * @param IQueryBuilder $qb - * @param string $column + * @param string $columnName * @param mixed $value * @param mixed $paramType * @return string diff --git a/src/modules/modals/CreateRow.vue b/src/modules/modals/CreateRow.vue index c753dcf3c..b934756e4 100644 --- a/src/modules/modals/CreateRow.vue +++ b/src/modules/modals/CreateRow.vue @@ -8,7 +8,7 @@ -
+
diff --git a/src/shared/components/ncTable/mixins/filter.js b/src/shared/components/ncTable/mixins/filter.js index e65dc3206..34f796482 100644 --- a/src/shared/components/ncTable/mixins/filter.js +++ b/src/shared/components/ncTable/mixins/filter.js @@ -49,7 +49,7 @@ export const Filters = { Contains: new Filter({ id: FilterIds.Contains, label: t('tables', 'Contains'), - goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLong, ColumnTypes.TextLink, ColumnTypes.TextRich], + goodFor: [ColumnTypes.TextLine, ColumnTypes.TextLong, ColumnTypes.TextLink, ColumnTypes.TextRich, ColumnTypes.SelectionMulti], incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual], }), BeginsWith: new Filter({ @@ -68,7 +68,7 @@ export const Filters = { id: FilterIds.IsEqual, label: t('tables', 'Is equal'), shortLabel: '=', - goodFor: [ColumnTypes.TextLine, ColumnTypes.Number, ColumnTypes.SelectionCheck, ColumnTypes.TextLink, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection], + goodFor: [ColumnTypes.TextLine, ColumnTypes.Number, ColumnTypes.SelectionCheck, ColumnTypes.TextLink, ColumnTypes.NumberStars, ColumnTypes.NumberProgress, ColumnTypes.DatetimeDate, ColumnTypes.DatetimeTime, ColumnTypes.Datetime, ColumnTypes.Selection, ColumnTypes.SelectionMulti], incompatibleWith: [FilterIds.IsEmpty, FilterIds.IsEqual, FilterIds.BeginsWith, FilterIds.EndsWith, FilterIds.Contains, FilterIds.IsGreaterThan, FilterIds.IsGreaterThanOrEqual, FilterIds.IsLowerThan, FilterIds.IsLowerThanOrEqual], }), IsGreaterThan: new Filter({