From 68ccfde3f30084bb394a2e1d8f66b08d23f0abf8 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 18 Jul 2022 16:23:36 +0300 Subject: [PATCH 1/3] max-size 0 fix --- .../Espo/Core/Select/SelectBuilder.php | 2 +- .../ORM/QueryComposer/BaseQueryComposer.php | 4 +- .../Espo/Core/Select/SelectBuilderTest.php | 19 +++++ .../unit/Espo/ORM/MysqlQueryComposerTest.php | 82 ++++++++++++++++++- 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/application/Espo/Core/Select/SelectBuilder.php b/application/Espo/Core/Select/SelectBuilder.php index a3b69468e7..1378294ece 100644 --- a/application/Espo/Core/Select/SelectBuilder.php +++ b/application/Espo/Core/Select/SelectBuilder.php @@ -468,7 +468,7 @@ class SelectBuilder $this->withDefaultOrder(); } - if ($this->searchParams->getMaxSize() || $this->searchParams->getOffset()) { + if ($this->searchParams->getMaxSize() !== null || $this->searchParams->getOffset() !== null) { $this->createLimitApplier() ->apply( $this->queryBuilder, diff --git a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php index 1c4d887ba6..1b8ab1c07b 100644 --- a/application/Espo/ORM/QueryComposer/BaseQueryComposer.php +++ b/application/Espo/ORM/QueryComposer/BaseQueryComposer.php @@ -3478,7 +3478,7 @@ abstract class BaseQueryComposer implements QueryComposer $sql .= " ORDER BY {$order}"; } - if ($limit) { + if ($limit !== null) { $sql = $this->limit($sql, null, $limit); } @@ -3510,7 +3510,7 @@ abstract class BaseQueryComposer implements QueryComposer $sql .= " ORDER BY {$order}"; } - if ($limit) { + if ($limit !== null) { $sql = $this->limit($sql, null, $limit); } diff --git a/tests/unit/Espo/Core/Select/SelectBuilderTest.php b/tests/unit/Espo/Core/Select/SelectBuilderTest.php index fc37c9d671..71fb1a00d2 100644 --- a/tests/unit/Espo/Core/Select/SelectBuilderTest.php +++ b/tests/unit/Espo/Core/Select/SelectBuilderTest.php @@ -282,4 +282,23 @@ class SelectBuilderTest extends \PHPUnit\Framework\TestCase $this->assertEquals($this->entityType, $query->getFrom()); } + + public function testBuildMaxSize0() + { + + $searchParams = SearchParams::create()->withMaxSize(0); + + + $this->limitApplier + ->expects($this->once()) + ->method('apply') + ->with($this->isInstanceOf(QueryBuilder::class), 0, 0); + + $query = $this->selectBuilder + ->from($this->entityType) + ->withSearchParams($searchParams) + ->build(); + + $this->assertEquals($this->entityType, $query->getFrom()); + } } diff --git a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php index e6fc1007fc..dd0b611224 100644 --- a/tests/unit/Espo/ORM/MysqlQueryComposerTest.php +++ b/tests/unit/Espo/ORM/MysqlQueryComposerTest.php @@ -150,7 +150,7 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } - public function testDeleteWithLimit() + public function testDeleteWithLimit1() { $sql = $this->query->compose(Delete::fromRaw([ 'from' => 'Account', @@ -170,6 +170,26 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } + public function testDeleteWithLimit0() + { + $sql = $this->query->compose(Delete::fromRaw([ + 'from' => 'Account', + 'whereClause' => [ + 'name' => 'test', + ], + 'orderBy' => 'name', + 'limit' => 0, + ])); + + $expectedSql = + "DELETE FROM `account` " . + "WHERE account.name = 'test' " . + "ORDER BY account.name ASC " . + "LIMIT 0"; + + $this->assertEquals($expectedSql, $sql); + } + public function testDeleteWithAlias() { $query = $this->queryBuilder @@ -206,6 +226,28 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } + public function testUpdateQuery0() + { + $sql = $this->query->compose(Update::fromRaw([ + 'from' => 'Account', + 'whereClause' => [ + 'name' => 'test', + ], + 'set' => [ + 'deleted' => false, + 'name' => 'hello', + ], + 'limit' => 0, + ])); + + $expectedSql = + "UPDATE `account` " . + "SET account.deleted = 0, account.name = 'hello' ". + "WHERE account.name = 'test' LIMIT 0"; + + $this->assertEquals($expectedSql, $sql); + } + public function testUpdateQuery1() { $sql = $this->query->compose(Update::fromRaw([ @@ -383,6 +425,40 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase $this->assertEquals($expectedSql, $sql); } + public function testSelectLimit1() + { + $select = $this->queryBuilder + ->select(['id']) + ->from('Account') + ->limit(1, 0) + ->build(); + + $sql = $this->query->compose($select); + + $expectedSql = + "SELECT account.id AS `id` FROM `account` " . + "WHERE account.deleted = 0 LIMIT 1, 0"; + + $this->assertEquals($expectedSql, $sql); + } + + public function testSelectLimit2() + { + $select = $this->queryBuilder + ->select(['id']) + ->from('Account') + ->limit(null, 0) + ->build(); + + $sql = $this->query->compose($select); + + $expectedSql = + "SELECT account.id AS `id` FROM `account` " . + "WHERE account.deleted = 0 LIMIT 0, 0"; + + $this->assertEquals($expectedSql, $sql); + } + public function testSelectAllColumns1() { $select = $this->queryBuilder @@ -2297,7 +2373,7 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase ->all() ->query($q1) ->query($q2) - ->limit(0, 2) + ->limit(0, 0) ->order(1, 'DESC') ->build(); @@ -2308,7 +2384,7 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase "UNION ALL ". "(SELECT 'test2' AS `value`) ". "ORDER BY 1 DESC ". - "LIMIT 0, 2"; + "LIMIT 0, 0"; $this->assertEquals($expectedSql, $sql); } From 1722e43fa8467c069278c3a52ebea49d7fa5313f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 26 Jul 2022 12:24:49 +0300 Subject: [PATCH 2/3] dashboard layout settings refactor --- .../preferences/fields/dashboard-tab-list.js | 79 +++++++++++++------ .../views/settings/fields/dashboard-layout.js | 62 +++++++++------ 2 files changed, 92 insertions(+), 49 deletions(-) diff --git a/client/src/views/preferences/fields/dashboard-tab-list.js b/client/src/views/preferences/fields/dashboard-tab-list.js index 50c90938d6..82ac0477c0 100644 --- a/client/src/views/preferences/fields/dashboard-tab-list.js +++ b/client/src/views/preferences/fields/dashboard-tab-list.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/preferences/fields/dashboard-tab-list', 'views/fields/array', function (Dep) { +define('views/preferences/fields/dashboard-tab-list', ['views/fields/array'], function (Dep) { return Dep.extend({ @@ -34,40 +34,71 @@ define('views/preferences/fields/dashboard-tab-list', 'views/fields/array', func Dep.prototype.setup.call(this); this.translatedOptions = {}; - var list = this.model.get(this.name) || []; - list.forEach(function (value) { + + let list = this.model.get(this.name) || []; + + list.forEach(value => { this.translatedOptions[value] = value; - }, this); + }); }, + getItemHtml: function (value) { value = value.toString(); - var valueSanitized = this.escapeValue(value); - var translatedValue = this.escapeValue(this.translatedOptions[value] || value); - var html = '' + - ''; + let translatedValue = this.translatedOptions[value] || value; - return html; + return $('
') + .addClass('list-group-item link-with-role form-inline') + .attr('data-value', value) + .append( + $('
') + .addClass('pull-left') + .css('width', '92%') + .css('display', 'inline-block') + .append( + $('') + .attr('data-name', 'translatedValue') + .attr('data-value', value) + .addClass('role form-control input-sm') + .attr('value', translatedValue) + ) + ) + .append( + $('
') + .css('width', '8%') + .css('display', 'inline-block') + .css('vertical-align', 'top') + .append( + $('') + .attr('href', 'javascript:') + .addClass('pull-right') + .attr('data-value', value) + .attr('data-action', 'removeValue') + .append( + $('').addClass('fas fa-times') + ) + ) + ) + .append( + $('
').css('clear', 'both') + ) + .get(0).outerHTML; }, fetch: function () { - var data = Dep.prototype.fetch.call(this); + let data = Dep.prototype.fetch.call(this); + data.translatedOptions = {}; - (data[this.name] || []).forEach(function (value) { - var valueInternal = value.replace(/"/g, '\\"'); - data.translatedOptions[value] = this.$el.find('input[data-name="translatedValue"][data-value="'+valueInternal+'"]').val() || value; - }, this); + + (data[this.name] || []).forEach(value => { + let valueInternal = value.replace(/"/g, '\\"'); + + data.translatedOptions[value] = this.$el + .find('input[data-name="translatedValue"][data-value="'+valueInternal+'"]') + .val() || value; + }); return data; - } - + }, }); - }); diff --git a/client/src/views/settings/fields/dashboard-layout.js b/client/src/views/settings/fields/dashboard-layout.js index 3637b6e5b6..22d00bee0a 100644 --- a/client/src/views/settings/fields/dashboard-layout.js +++ b/client/src/views/settings/fields/dashboard-layout.js @@ -182,8 +182,6 @@ define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!grid layout.forEach((o, i) => { if (o.id === id) { layout.splice(i, 1); - - return; } }); @@ -204,7 +202,6 @@ define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!grid var dashboardLayout = []; - (data.dashboardTabList).forEach((name) => { var layout = []; var id = this.generateId(); @@ -371,37 +368,52 @@ define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!grid var actionsHtml = ''; var actions2Html = ''; - if (this.mode === 'edit') { - actionsHtml += - '
'+ - ''+ - ''; + if (this.isEditMode()) { + actionsHtml += $('') + .attr('href', 'javascript:') + .addClass('pull-right') + .attr('data-action', 'removeDashlet') + .attr('data-id', id) + .append( + $('').addClass('fas fa-times') + ) + .get(0) + .outerHTML; - actions2Html += - ''+ - this.translate('Edit') + - ''; + actions2Html += $('') + .attr('href', 'javascript:') + .addClass('pull-right') + .attr('data-action', 'editDashlet') + .attr('data-id', id) + .attr('data-name', name) + .text(this.translate('Edit')) + .get(0) + .outerHTML; } - var title = this.getOption(id, 'title'); - - if (title) { - title = this.getHelper().escapeString(title); - } + let title = this.getOption(id, 'title'); if (!title) { title = this.translate(name, 'dashlets'); } - var headerHtml = - '
' + - actionsHtml + '

' + title + '

' + - '
'; + let headerHtml = $('
') + .addClass('panel-heading') + .append(actionsHtml) + .append( + $('

').addClass('panel-title').text(title) + ) + .get(0).outerHTML; - var $container = $( - '
' + headerHtml + - '
'+actions2Html+'
' - ); + let $container = + $('
') + .addClass('grid-stack-item-content panel panel-default') + .append(headerHtml) + .append( + $('
') + .addClass('panel-body') + .append(actions2Html) + ); $container.attr('data-id', id); $container.attr('data-name', name); From b98bbd8d4c1a2fbaceba4aae3a02ffbb38f72866 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Tue, 26 Jul 2022 12:26:24 +0300 Subject: [PATCH 3/3] v --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index ece48af0b7..18c3e14922 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "7.1.9", + "version": "7.1.10", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index 00eef68437..76cc527e7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "espocrm", - "version": "7.1.9", + "version": "7.1.10", "description": "Open-source CRM.", "repository": { "type": "git",