From 32c16c888c8dd047220c15c9cf75d30013b883e5 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Tue, 23 Nov 2021 15:39:49 +0100 Subject: [PATCH 01/42] Filters blank/null/empty + Tests in different dbs --- .github/workflows/php.yml | 40 +++- composer.json | 22 ++- phpunit.mysql.xml | 22 +++ phpunit.pgsql.xml | 26 +++ phpunit.sqlite.xml | 26 +++ phpunit.sqlsrv.xml | 22 +++ phpunit.xml | 12 +- resources/lang/en/datatable.php | 7 + .../components/filters/input-text.blade.php | 6 + src/Helpers/Collection.php | 47 ++++- src/Helpers/Model.php | 43 ++++- src/Helpers/SqlSupport.php | 19 ++ tests/DishesCollectionTable.php | 13 ++ tests/DishesTable.php | 13 +- tests/Feature/ColumnTest.php | 9 +- .../FilterInputOptionsCollectionTest.php | 151 +++++++++++++++ tests/Feature/FilterInputOptionsModelTest.php | 152 +++++++++++++++ tests/Feature/FilterNameOptionsTest.php | 181 ------------------ tests/Pest.php | 12 ++ tests/TestCase.php | 24 ++- 20 files changed, 630 insertions(+), 217 deletions(-) create mode 100644 phpunit.mysql.xml create mode 100644 phpunit.pgsql.xml create mode 100644 phpunit.sqlite.xml create mode 100644 phpunit.sqlsrv.xml create mode 100644 src/Helpers/SqlSupport.php create mode 100644 tests/Feature/FilterInputOptionsCollectionTest.php create mode 100644 tests/Feature/FilterInputOptionsModelTest.php delete mode 100644 tests/Feature/FilterNameOptionsTest.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 4e27c03f..737c34ad 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -12,6 +12,27 @@ on: jobs: build: runs-on: ubuntu-latest + + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: powergridtest + ports: + - 3308:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + + postgres: + image: postgres:10.8 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: powergridtest + ports: + - 5433:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + strategy: matrix: php: [ 7.4, 8.0 ] @@ -49,10 +70,19 @@ jobs: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Cs Fixer - run: vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation + run: ./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation + + - name: Testing with SQLite + run: | + ./vendor/bin/pest --configuration phpunit.sqlite.xml + + - name: Testing with MySQL + run: | + ./vendor/bin/pest --configuration phpunit.mysql.xml - - name: Larastan - run: vendor/bin/phpstan analyse --ansi --memory-limit=-1 + - name: Testing with PostgreSQL + run: | + ./vendor/bin/pest --configuration phpunit.pgsql.xml - - name: Pest tests - run: vendor/bin/pest + - name: Larastan + run: ./vendor/bin/phpstan analyse --ansi --memory-limit=-1 \ No newline at end of file diff --git a/composer.json b/composer.json index 9a0b761d..b9266041 100644 --- a/composer.json +++ b/composer.json @@ -40,18 +40,22 @@ "friendsofphp/php-cs-fixer": "^3.2" }, "scripts": { - "stan": "phpstan analyse --ansi --memory-limit=-1", - "pest": "php ./vendor/bin/pest --colors=always", - "cs-check": "./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation", + "test": "@test:sqlite", + "test:sqlite": "./vendor/bin/pest --configuration phpunit.sqlite.xml", + "test:mysql": "./vendor/bin/pest --configuration phpunit.mysql.xml", + "test:pgsql": "./vendor/bin/pest --configuration phpunit.pgsql.xml", + "test:types": "./vendor/bin/phpstan analyse --ansi --memory-limit=-1", + "cs-fixer": "./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation", "fix": "./vendor/bin/php-cs-fixer fix", - "test": [ - "@pest", - "@stan", - "@cs-check" - ], + "dbs": [ + "@test:sqlite", + "@test:mysql", + "@test:pgsql" + ], "check": [ "@cs-fixer", - "@test" + "@test", + "@test:types" ] }, "require-dev": { diff --git a/phpunit.mysql.xml b/phpunit.mysql.xml new file mode 100644 index 00000000..d08cce57 --- /dev/null +++ b/phpunit.mysql.xml @@ -0,0 +1,22 @@ + + + + + ./tests/Feature + + + + + ./app + + + + + + + + + + + + diff --git a/phpunit.pgsql.xml b/phpunit.pgsql.xml new file mode 100644 index 00000000..573bd7f3 --- /dev/null +++ b/phpunit.pgsql.xml @@ -0,0 +1,26 @@ + + + + + ./tests/Feature + + + + + ./app + + + + + + + + + + + + \ No newline at end of file diff --git a/phpunit.sqlite.xml b/phpunit.sqlite.xml new file mode 100644 index 00000000..99ba2c06 --- /dev/null +++ b/phpunit.sqlite.xml @@ -0,0 +1,26 @@ + + + + + ./tests/Feature + + + + + ./app + + + + + + + + + + + + diff --git a/phpunit.sqlsrv.xml b/phpunit.sqlsrv.xml new file mode 100644 index 00000000..978e5d66 --- /dev/null +++ b/phpunit.sqlsrv.xml @@ -0,0 +1,22 @@ + + + + + ./tests/Feature + + + + + ./app + + + + + + + + + + + + diff --git a/phpunit.xml b/phpunit.xml index d482b36c..61513eaf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -5,9 +5,6 @@ colors="true" > - - ./tests - ./tests/Feature @@ -18,4 +15,13 @@ ./src + + + + + + + + + diff --git a/resources/lang/en/datatable.php b/resources/lang/en/datatable.php index ea61797f..819be721 100644 --- a/resources/lang/en/datatable.php +++ b/resources/lang/en/datatable.php @@ -41,6 +41,13 @@ 'contains_not' => 'Does not contain', 'starts_with' => 'Starts with', 'ends_with' => 'Ends with', + 'is_empty' => 'Is empty', + 'is_not_empty' => 'Is not empty', + 'is_null' => 'Is null', + 'is_not_null' => 'Is not null', + 'is_blank' => 'Is blank', + 'is_not_blank' => 'Is not blank', + ], 'export' => [ 'exporting' => 'Please wait!', diff --git a/resources/views/components/filters/input-text.blade.php b/resources/views/components/filters/input-text.blade.php index 99779c7e..5c804a41 100644 --- a/resources/views/components/filters/input-text.blade.php +++ b/resources/views/components/filters/input-text.blade.php @@ -24,6 +24,12 @@ class="power_grid {{ $theme->selectClass }} {{ data_get($column, 'headerClass') + + + + + +
diff --git a/src/Helpers/Collection.php b/src/Helpers/Collection.php index 0f7dc15f..3285387b 100644 --- a/src/Helpers/Collection.php +++ b/src/Helpers/Collection.php @@ -138,9 +138,6 @@ public function filter(): BaseCollection foreach ($this->filters as $key => $type) { foreach ($type as $field => $value) { - if (!filled($value)) { - continue; - } switch ($key) { case 'date_picker': $this->filterDatePicker($field, $value); @@ -195,7 +192,7 @@ private function validateInputTextOptions(string $field): bool { return isset($this->filters['input_text_options'][$field]) && in_array( strtolower($this->filters['input_text_options'][$field]), - ['is', 'is_not', 'contains', 'contains_not', 'starts_with', 'ends_with'] + ['is', 'is_not', 'contains', 'contains_not', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'is_blank', 'is_not_blank'] ); } @@ -250,6 +247,48 @@ public function filterInputText(string $field, string $value): void }); break; + + case 'is_blank': + $this->query = $this->query->whereNotNull($field)->where($field, '=', ''); + + break; + + case 'is_not_blank': + $this->query = $this->query->filter(function ($row) use ($field) { + $row = (object) $row; + + return $row->{$field} != '' || is_null($row->{$field}); + }); + + break; + + case 'is_null': + $this->query = $this->query->whereNull($field); + + break; + + case 'is_not_null': + $this->query = $this->query->whereNotNull($field); + + break; + + case 'is_empty': + $this->query = $this->query->filter(function ($row) use ($field) { + $row = (object) $row; + + return $row->{$field} == '' || is_null($row->{$field}); + }); + + break; + + case 'is_not_empty': + $this->query = $this->query->filter(function ($row) use ($field) { + $row = (object) $row; + + return $row->{$field} !== '' && !is_null($row->{$field}); + }); + + break; } } diff --git a/src/Helpers/Model.php b/src/Helpers/Model.php index b373d9e1..6a405ea6 100644 --- a/src/Helpers/Model.php +++ b/src/Helpers/Model.php @@ -194,7 +194,6 @@ public function filterInputText(Builder $query, string $field, $value): void } $textFieldOperator = ($this->validateInputTextOptions($field) ? strtolower($this->filters['input_text_options'][$field]) : 'contains'); - switch ($textFieldOperator) { case 'is': $query->where($field, '=', $value); @@ -205,22 +204,46 @@ public function filterInputText(Builder $query, string $field, $value): void break; case 'starts_with': - $query->where($field, 'like', $value . '%'); + $query->where($field, SqlSupport::like(), $value . '%'); break; case 'ends_with': - $query->where($field, 'like', '%' . $value); + $query->where($field, SqlSupport::like(), '%' . $value); break; case 'contains': - $query->where($field, 'like', '%' . $value . '%'); + $query->where($field, SqlSupport::like(), '%' . $value . '%'); break; case 'contains_not': - $query->where($field, 'not like', '%' . $value . '%'); + $query->where($field, 'NOT ' . SqlSupport::like(), '%' . $value . '%'); break; - } + case 'is_empty': + $query->where($field, '=', '')->orWhereNull($field); + + break; + case 'is_not_empty': + $query->where($field, '!=', '')->whereNotNull($field); + + break; + case 'is_null': + $query->whereNull($field); + + break; + case 'is_not_null': + $query->whereNotNull($field); + + break; + case 'is_blank': + $query->where($field, '=', ''); + + break; + case 'is_not_blank': + $query->where($field, '!=', '')->orWhereNull($field); + + break; + } } /** @@ -233,7 +256,7 @@ private function validateInputTextOptions(string $field): bool { return isset($this->filters['input_text_options'][$field]) && in_array( strtolower($this->filters['input_text_options'][$field]), - ['is', 'is_not', 'contains', 'contains_not', 'starts_with', 'ends_with'] + ['is', 'is_not', 'contains', 'contains_not', 'starts_with', 'ends_with', 'is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'is_blank', 'is_not_blank'] ); } @@ -285,7 +308,7 @@ public function filterContains(): Model $hasColumn = Schema::hasColumn($table, $field); if ($hasColumn) { - $query->orWhere($table . '.' . $field, 'like', '%' . $this->search . '%'); + $query->orWhere($table . '.' . $field, SqlSupport::like(), '%' . $this->search . '%'); } } } @@ -316,13 +339,13 @@ private function filterRelation(): void if ($query->getRelation($nestedTable) != '') { foreach ($column as $nestedColumn) { $this->query = $this->query->orWhereHas($table . '.' . $nestedTable, function (Builder $query) use ($nestedColumn) { - $query->where($nestedColumn, 'like', '%' . $this->search . '%'); + $query->where($nestedColumn, SqlSupport::like(), '%' . $this->search . '%'); }); } } } else { $this->query = $this->query->orWhereHas($table, function (Builder $query) use ($column) { - $query->where($column, 'like', '%' . $this->search . '%'); + $query->where($column, SqlSupport::like(), '%' . $this->search . '%'); }); } } diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php new file mode 100644 index 00000000..05dfcb2d --- /dev/null +++ b/src/Helpers/SqlSupport.php @@ -0,0 +1,19 @@ + 'ILIKE', + ]; + + return $likeSyntax[$driverName] ?? 'LIKE'; + } +} diff --git a/tests/DishesCollectionTable.php b/tests/DishesCollectionTable.php index 45063daf..b075bbfd 100644 --- a/tests/DishesCollectionTable.php +++ b/tests/DishesCollectionTable.php @@ -19,6 +19,7 @@ public function datasource(): Collection 'price' => 1.58, 'in_stock' => true, 'created_at' => '2021-01-01 00:00:00', + 'chef_name' => '', ], [ 'id' => 2, @@ -26,6 +27,7 @@ public function datasource(): Collection 'price' => 1.68, 'in_stock' => true, 'created_at' => '2021-02-02 00:00:00', + 'chef_name' => null, ], [ 'id' => 3, @@ -33,6 +35,7 @@ public function datasource(): Collection 'price' => 1.78, 'in_stock' => false, 'created_at' => '2021-03-03 00:00:00', + 'chef_name' => 'Luan', ], [ 'id' => 4, @@ -40,6 +43,7 @@ public function datasource(): Collection 'price' => 1.88, 'in_stock' => true, 'created_at' => '2021-04-04 00:00:00', + 'chef_name' => 'Luan', ], [ 'id' => 5, @@ -47,6 +51,7 @@ public function datasource(): Collection 'price' => 1.98, 'in_stock' => false, 'created_at' => '2021-05-05 00:00:00', + 'chef_name' => 'Luan', ], ]); } @@ -64,6 +69,7 @@ public function addColumns(): PowerGridEloquent return PowerGrid::eloquent() ->addColumn('id') ->addColumn('name') + ->addColumn('chef_name') ->addColumn('price') ->addColumn('in_stock') ->addColumn('in_stock_label', function ($entry) { @@ -90,6 +96,13 @@ public function columns(): array ->makeInputText('name') ->sortable(), + Column::add() + ->title(__('Chef')) + ->field('chef_name') + ->searchable() + ->makeInputText('chef_name') + ->sortable(), + Column::add() ->title(__('Price')) ->field('price') diff --git a/tests/DishesTable.php b/tests/DishesTable.php index ad57ff07..0267a6a6 100644 --- a/tests/DishesTable.php +++ b/tests/DishesTable.php @@ -58,6 +58,7 @@ public function addColumns(): ?PowerGridEloquent ->addColumn('id') ->addColumn('name') ->addColumn('storage_room') + ->addColumn('chef_name') ->addColumn('calories') ->addColumn('calories', function (Dish $dish) { return $dish->calories . ' kcal'; @@ -117,6 +118,16 @@ public function columns(): array ->placeholder('Prato placeholder') ->sortable(), + Column::add() + ->title(__('Chef')) + ->field('chef_name') + ->searchable() + ->editOnClick($canEdit) + ->clickToCopy(true) + ->makeInputText('chef_name') + ->placeholder('Chef placeholder') + ->sortable(), + Column::add() ->title(__('Categoria')) ->field('category_name') @@ -188,7 +199,7 @@ public function update(array $data): bool $updated = false; } - return true; + return $updated; } public function updateMessages(string $status, string $field = '_default_message'): string diff --git a/tests/Feature/ColumnTest.php b/tests/Feature/ColumnTest.php index 82f06b25..03cf5422 100644 --- a/tests/Feature/ColumnTest.php +++ b/tests/Feature/ColumnTest.php @@ -11,7 +11,14 @@ ->call('sortBy', 'id') ->assertSeeHtml('Pastel de Nata'); -it('searches data') + it('searches data') + ->livewire(DishesTable::class) + ->assertSeeHtml('Pastel de Nata') + ->set('search', 'Sugo') + ->assertSeeHtml('Almôndegas ao Sugo') + ->assertDontSeeHtml('Pastel de Nata'); + +it('searches data as case insensitive') ->livewire(DishesTable::class) ->assertSeeHtml('Pastel de Nata') ->set('search', 'sugo') diff --git a/tests/Feature/FilterInputOptionsCollectionTest.php b/tests/Feature/FilterInputOptionsCollectionTest.php new file mode 100644 index 00000000..42b97f4d --- /dev/null +++ b/tests/Feature/FilterInputOptionsCollectionTest.php @@ -0,0 +1,151 @@ +livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 1', 'is')) + ->assertSee('Name 1') + ->assertDontSee('Name 2') + ->call('clearFilter', 'name') + ->assertSeeText('Name 1'); + +it('properly filters by "name is" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 6', 'is')) + ->assertSee('No records found') + ->assertDontSee('Name 1'); + +it('properly filters by "name is not" using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 2', 'is_not')) + ->assertSee('Name 1') + ->assertDontSee('NAme 2') + ->call('clearFilter', 'name') + ->assertSee('Name 2'); + +it('properly filters by "name is not" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 6', 'is_not')) + ->assertSee('Name 5') + ->assertSee('Name 4') + ->call('clearFilter', 'name') + ->assertViewHas('filters', []); + +it('properly filters by "name contains" using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('4', 'contains')) + ->assertSee('Name 4') + ->assertDontSee('Name 2'); + +it('properly filters by "name contains" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 6', 'contains')) + ->assertSee('No records found') + ->assertDontSee('Name 1') + ->assertDontSee('Name 2'); + +it('properly filters by "name contains not" using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('5', 'contains_not')) + ->assertDontSee('Name 5') + ->assertSee('Name 1'); + +it('properly filters by "name contains not" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Name 6', 'contains_not')) + ->assertSee('Name 1') + ->assertSee('Name 2'); + +it('properly filters by "name starts with" using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Na', 'starts_with')) + ->assertSee('Name 1') + ->assertSee('Name 2'); + +it('properly filters by "name starts with" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Nonexistent', 'starts_with')) + ->assertSee('No records found') + ->assertDontSee('Name 1') + ->assertDontSee('Name 2'); + +it('properly filters by "name ends with" using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('e 5', 'ends_with')) + ->assertSee('Name 5') + ->assertDontSee('Name 1'); + +it('properly filters by "name ends with" using nonexistent record using collection table') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('Nonexistent', 'ends_with')) + ->assertSee('No records found') + ->assertDontSee('Name 1') + ->assertDontSee('Name 2') + ->assertDontSee('Name 3'); + +it('properly filters by "chef name is blank"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_blank', 'chef_name')) + ->assertSee('Name 1') + ->assertDontSee('Name 2') + ->assertDontSee('Name 3') + ->set('perPage', '50') + ->assertSee('Name 1') + ->assertDontSee('Name 2') + ->assertDontSee('Name 3'); + +it('properly filters by "chef name is NOT blank"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_not_blank', 'chef_name')) + ->assertSee('Name 2') + ->assertSee('Name 3') + ->assertDontSee('Name 1') + ->set('perPage', '50') + ->assertSee('Name 2') + ->assertSee('Name 3') + ->assertDontSee('Name 1'); + +it('properly filters by "chef name is null"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_null', 'chef_name')) + ->assertSee('Name 2') + ->assertDontSee('Name 1') + ->assertDontSee('Name 3') + ->set('perPage', '50') + ->assertSee('Name 2') + ->assertDontSee('Name 1') + ->assertDontSee('Name 3'); + +it('properly filters by "chef name is NOT null"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_not_null', 'chef_name')) + ->assertSee('Name 1') + ->assertSee('Name 3') + ->assertDontSee('Name 2') + ->set('perPage', '50') + ->assertSee('Name 1') + ->assertSee('Name 3') + ->assertDontSee('Name 2'); + +it('properly filters by "chef name is empty"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_empty', 'chef_name')) + ->assertSee('Name 1') + ->assertSee('Name 2') + ->assertDontSee('Name 3') + ->set('perPage', '50') + ->assertSee('Name 1') + ->assertSee('Name 2') + ->assertDontSee('Name 3'); + +it('properly filters by "chef name is NOT empty"') + ->livewire(DishesCollectionTable::class) + ->set('filters', filterInputText('', 'is_not_empty', 'chef_name')) + ->assertSee('Name 3') + ->assertDontSee('Name 1') + ->assertDontSee('Name 2') + ->set('perPage', '50') + ->assertSee('Name 3') + ->assertDontSee('Name 1') + ->assertDontSee('Name 2'); diff --git a/tests/Feature/FilterInputOptionsModelTest.php b/tests/Feature/FilterInputOptionsModelTest.php new file mode 100644 index 00000000..4429b71b --- /dev/null +++ b/tests/Feature/FilterInputOptionsModelTest.php @@ -0,0 +1,152 @@ +livewire(DishesTable::class) + ->set('filters', filterInputText('Francesinha', 'is')) + ->assertSee('Francesinha') + ->assertDontSee('Francesinha vegana') + ->call('clearFilter', 'name') + ->assertSee('Francesinha vegana'); + +it('properly filters by "name is" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent dish', 'is')) + ->assertSee('No records found') + ->assertDontSee('Francesinha'); + +it('properly filters by "name is not"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Francesinha vegana', 'is_not')) + ->assertSee('Francesinha') + ->assertDontSee('Francesinha vegana') + ->call('clearFilter', 'name') + ->assertSee('Francesinha vegana'); + +it('properly filters by "name is not" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent dish', 'is_not')) + ->assertSee('Francesinha') + ->assertSee('Francesinha vegana') + ->call('clearFilter', 'name') + ->assertViewHas('filters', []); + +it('properly filters by "name contains"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('francesinha', 'contains')) + ->assertSee('Francesinha') + ->assertSee('Francesinha vegana'); + +it('properly filters by "name contains" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent dish', 'contains')) + ->assertSee('No records found') + ->assertDontSee('Francesinha') + ->assertDontSee('Francesinha vegana'); + +it('properly filters by "name contains not"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('francesinha', 'contains_not')) + ->assertDontSee('Francesinha') + ->assertDontSee('Francesinha vegana'); + +it('properly filters by "name contains not" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent dish', 'contains_not')) + ->assertSee('Francesinha') + ->assertSee('Francesinha vegana'); + +it('properly filters by "name starts with"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('fran', 'starts_with')) + ->assertSee('Francesinha') + ->assertSee('Francesinha vegana') + ->assertDontSee('Barco-Sushi da Sueli'); + +it('properly filters by "name starts with" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent', 'starts_with')) + ->assertSee('No records found') + ->assertDontSee('Francesinha') + ->assertDontSee('Francesinha vegana') + ->assertDontSee('Barco-Sushi da Sueli'); + +it('properly filters by "name ends with"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('vegana', 'ends_with')) + ->assertSee('Francesinha vegana') + ->assertDontSee('Barco-Sushi da Sueli'); + +it('properly filters by "name ends with" using nonexistent record') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('Nonexistent', 'ends_with')) + ->assertSee('No records found') + ->assertDontSee('Francesinha') + ->assertDontSee('Francesinha vegana') + ->assertDontSee('Barco-Sushi da Sueli'); + +it('properly filters by "chef name is blank"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_blank', 'chef_name')) + ->assertSee('Carne Louca') + ->assertDontSee('Pastel de Nata') + ->assertDontSee('Francesinha vegana') + ->set('perPage', '50') + ->assertSee('Carne Louca') + ->assertDontSee('Pastel de Nata') + ->assertDontSee('Francesinha vegana'); + +it('properly filters by "chef name is NOT blank"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_not_blank', 'chef_name')) + ->assertSee('Pastel de Nata') + ->assertSee('Francesinha vegana') + ->assertDontSee('Carne Louca') + ->set('perPage', '50') + ->assertSee('Pastel de Nata') + ->assertSee('Francesinha vegana') + ->assertDontSee('Carne Louca'); + +it('properly filters by "chef name is null"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_null', 'chef_name')) + ->assertSee('Pastel de Nata') + ->assertDontSee('Francesinha vegana') + ->assertDontSee('Carne Louca') + ->set('perPage', '50') + ->assertSee('Pastel de Nata') + ->assertDontSee('Francesinha vegana') + ->assertDontSee('Carne Louca'); + +it('properly filters by "chef name is NOT null"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_not_null', 'chef_name')) + ->assertSee('Francesinha vegana') + ->assertSee('Carne Louca') + ->assertDontSee('Pastel de Nata') + ->set('perPage', '50') + ->assertSee('Francesinha vegana') + ->assertSee('Carne Louca') + ->assertDontSee('Pastel de Nata'); + +it('properly filters by "chef name is empty"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_empty', 'chef_name')) + ->assertSee('Pastel de Nata') + ->assertSee('Carne Louca') + ->assertDontSee('Francesinha vegana') + ->set('perPage', '50') + ->assertSee('Pastel de Nata') + ->assertSee('Carne Louca') + ->assertDontSee('Francesinha vegana'); + +it('properly filters by "chef name is NOT empty"') + ->livewire(DishesTable::class) + ->set('filters', filterInputText('', 'is_not_empty', 'chef_name')) + ->assertSee('Francesinha vegana') + ->assertDontSee('Pastel de Nata') + ->assertDontSee('Carne Louca') + ->assertSee('Francesinha vegana') + ->assertDontSee('Pastel de Nata') + ->assertDontSee('Carne Louca'); diff --git a/tests/Feature/FilterNameOptionsTest.php b/tests/Feature/FilterNameOptionsTest.php deleted file mode 100644 index 75c24b8c..00000000 --- a/tests/Feature/FilterNameOptionsTest.php +++ /dev/null @@ -1,181 +0,0 @@ -livewire(DishesTable::class) - ->set('filters', filterInputText('Francesinha', 'is')) - ->assertSee('Francesinha') - ->assertDontSee('Francesinha vegana') - ->call('clearFilter', 'name') - ->assertSee('Francesinha vegana'); - -it('properly filters by "name is" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent dish', 'is')) - ->assertSee('No records found') - ->assertDontSee('Francesinha'); - -it('properly filters by "name is not"') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Francesinha vegana', 'is_not')) - ->assertSee('Francesinha') - ->assertDontSee('Francesinha vegana') - ->call('clearFilter', 'name') - ->assertSee('Francesinha vegana'); - -it('properly filters by "name is not" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent dish', 'is_not')) - ->assertSee('Francesinha') - ->assertSee('Francesinha vegana') - ->call('clearFilter', 'name') - ->assertViewHas('filters', []); - -it('properly filters by "name contains"') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('francesinha', 'contains')) - ->assertSee('Francesinha') - ->assertSee('Francesinha vegana'); - -it('properly filters by "name contains" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent dish', 'contains')) - ->assertSee('No records found') - ->assertDontSee('Francesinha') - ->assertDontSee('Francesinha vegana'); - -it('properly filters by "name contains not"') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('francesinha', 'contains_not')) - ->assertDontSee('Francesinha') - ->assertDontSee('Francesinha vegana'); - -it('properly filters by "name contains not" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent dish', 'contains_not')) - ->assertSee('Francesinha') - ->assertSee('Francesinha vegana'); - -it('properly filters by "name starts with"') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('fran', 'starts_with')) - ->assertSee('Francesinha') - ->assertSee('Francesinha vegana') - ->assertDontSee('Barco-Sushi da Sueli'); - -it('properly filters by "name starts with" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent', 'starts_with')) - ->assertSee('No records found') - ->assertDontSee('Francesinha') - ->assertDontSee('Francesinha vegana') - ->assertDontSee('Barco-Sushi da Sueli'); - -it('properly filters by "name ends with"') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('vegana', 'ends_with')) - ->assertSee('Francesinha vegana') - ->assertDontSee('Barco-Sushi da Sueli'); - -it('properly filters by "name ends with" using nonexistent record') - ->livewire(DishesTable::class) - ->set('filters', filterInputText('Nonexistent', 'ends_with')) - ->assertSee('No records found') - ->assertDontSee('Francesinha') - ->assertDontSee('Francesinha vegana') - ->assertDontSee('Barco-Sushi da Sueli'); - -it('properly filters by "name is" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 1', 'is')) - ->assertSee('Name 1') - ->assertDontSee('Name 2') - ->call('clearFilter', 'name') - ->assertSeeText('Name 1'); - -it('properly filters by "name is" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 6', 'is')) - ->assertSee('No records found') - ->assertDontSee('Name 1'); - -it('properly filters by "name is not" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 2', 'is_not')) - ->assertSee('Name 1') - ->assertDontSee('NAme 2') - ->call('clearFilter', 'name') - ->assertSee('Name 2'); - -it('properly filters by "name is not" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 6', 'is_not')) - ->assertSee('Name 5') - ->assertSee('Name 4') - ->call('clearFilter', 'name') - ->assertViewHas('filters', []); - -it('properly filters by "name contains" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('4', 'contains')) - ->assertSee('Name 4') - ->assertDontSee('Name 2'); - -it('properly filters by "name contains" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 6', 'contains')) - ->assertSee('No records found') - ->assertDontSee('Name 1') - ->assertDontSee('Name 2'); - -it('properly filters by "name contains not" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('5', 'contains_not')) - ->assertDontSee('Name 5') - ->assertSee('Name 1'); - -it('properly filters by "name contains not" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Name 6', 'contains_not')) - ->assertSee('Name 1') - ->assertSee('Name 2'); - -it('properly filters by "name starts with" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Na', 'starts_with')) - ->assertSee('Name 1') - ->assertSee('Name 2'); - -it('properly filters by "name starts with" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Nonexistent', 'starts_with')) - ->assertSee('No records found') - ->assertDontSee('Name 1') - ->assertDontSee('Name 2'); - -it('properly filters by "name ends with" using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('e 5', 'ends_with')) - ->assertSee('Name 5') - ->assertDontSee('Name 1'); - -it('properly filters by "name ends with" using nonexistent record using collection table') - ->livewire(DishesCollectionTable::class) - ->set('filters', filterInputText('Nonexistent', 'ends_with')) - ->assertSee('No records found') - ->assertDontSee('Name 1') - ->assertDontSee('Name 2') - ->assertDontSee('Name 3'); - -function filterInputText(string $text, string $type): array -{ - return [ - 'input_text' => [ - 'name' => $text, - ], - 'input_text_options' => [ - 'name' => $type, - ], - ]; -} diff --git a/tests/Pest.php b/tests/Pest.php index b47fa86e..ada39553 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -70,3 +70,15 @@ function powergridJoinCategory() return $component; } + +function filterInputText(string $text, string $type, $field = 'name'): array +{ + return [ + 'input_text' => [ + $field => $text, + ], + 'input_text_options' => [ + $field => $type, + ], + ]; +} diff --git a/tests/TestCase.php b/tests/TestCase.php index bd0fb7bb..f3894381 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,6 +23,9 @@ protected function setUp(): void protected function migrations(): void { + Schema::dropIfExists('dishes'); + Schema::dropIfExists('categories'); + Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); @@ -39,6 +42,7 @@ protected function migrations(): void $table->string('stored_at'); $table->boolean('active')->default(true); $table->datetime('produced_at'); + $table->string('chef_name')->nullable(); $table->timestamps(); }); } @@ -68,13 +72,18 @@ protected function seeders(array $dishes = []): void * @param Application $app * @return void */ - protected function getEnvironmentSetUp($app) + protected function getEnvironmentSetUp($app): void { $app['config']->set('database.default', 'testbench'); $app['config']->set('app.key', 'base64:RygUQvaR926QuH4d5G6ZDf9ToJEEeO2p8qDSCq6emPk='); + $app['config']->set('database.connections.testbench', [ - 'driver' => 'sqlite', - 'database' => ':memory:', + 'driver' => env('DB_DRIVER'), + 'host' => env('DB_HOST'), + 'port' => env('DB_PORT'), + 'username' => env('DB_USERNAME'), + 'password' => env('DB_PASSWORD'), + 'database' => env('DB_DATABASE'), 'prefix' => '', ]); } @@ -88,6 +97,7 @@ protected function getDishes(): array 'price' => 10.00, 'in_stock' => true, 'produced_at' => '2021-01-01 00:00:00', + 'chef_name' => null, ], [ 'name' => 'Peixada da chef Nábia', @@ -95,6 +105,8 @@ protected function getDishes(): array 'price' => 20.50, 'in_stock' => true, 'produced_at' => '2021-02-02 00:00:00', + 'chef_name' => 'Nábia', + ], [ 'name' => 'Carne Louca', @@ -102,6 +114,8 @@ protected function getDishes(): array 'price' => 30.00, 'in_stock' => true, 'produced_at' => '2021-03-03 00:00:00', + 'chef_name' => '', + ], [ 'name' => 'Bife à Rolê', @@ -273,6 +287,10 @@ protected function getDishes(): array $dish['price'] = $faker->randomFloat(2, 50, 200); }; + if (!array_key_exists('chef_name', $dish)) { + $dish['chef_name'] = 'Luan'; + } + return $dish; })->toArray(); } From bd27b38c1e74985104401f1ce0feaec3fd3a1d2f Mon Sep 17 00:00:00 2001 From: NathanaelGT Date: Wed, 24 Nov 2021 16:28:48 +0800 Subject: [PATCH 02/42] prevent xss attack --- src/PowerGridEloquent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PowerGridEloquent.php b/src/PowerGridEloquent.php index 5b146998..6b06dd40 100644 --- a/src/PowerGridEloquent.php +++ b/src/PowerGridEloquent.php @@ -32,7 +32,7 @@ public static function eloquent(): PowerGridEloquent */ public function addColumn(string $field, Closure $closure = null): PowerGridEloquent { - $this->columns[$field] = $closure ?? fn ($model) => $model->{$field}; + $this->columns[$field] = $closure ?? fn ($model) => e($model->{$field}); return $this; } From 29ccabc2edaef131e4f889bf89c21952ab1bf505 Mon Sep 17 00:00:00 2001 From: NathanaelGT Date: Wed, 24 Nov 2021 17:28:58 +0800 Subject: [PATCH 03/42] fix editable colum xss --- resources/views/assets/scripts.blade.php | 6 ++++++ resources/views/components/editable.blade.php | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/resources/views/assets/scripts.blade.php b/resources/views/assets/scripts.blade.php index 70417f4c..6293ac38 100644 --- a/resources/views/assets/scripts.blade.php +++ b/resources/views/assets/scripts.blade.php @@ -13,6 +13,12 @@ function copyToClipboard(button) { document.body.removeChild(el); } + function htmlSpecialChars(string) { + const el = document.createElement('div'); + el.innerText = string; + return el.innerHTML; + } + function isV2() { return window.Alpine && window.Alpine.version && /^2\..+\..+$/.test(window.Alpine.version) } diff --git a/resources/views/components/editable.blade.php b/resources/views/components/editable.blade.php index 02242397..3cd1af4d 100644 --- a/resources/views/components/editable.blade.php +++ b/resources/views/components/editable.blade.php @@ -12,7 +12,7 @@ field: '{{ $field }}', content: '{{ addslashes($row->{$field}) }}' }"> -
+ :value="$root.firstElementChild.innerText">
From 1dfa39d350931c486a977eb312bdbd58aa843e05 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 12:30:57 -0300 Subject: [PATCH 04/42] fix: primitive type of the `name` property changed to `string`. --- tests/Models/Category.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Models/Category.php b/tests/Models/Category.php index 8c75a3b4..b6774ca9 100644 --- a/tests/Models/Category.php +++ b/tests/Models/Category.php @@ -7,7 +7,7 @@ /** * @property int $id - * @property int $name + * @property string $name * @property Carbon $created_at * @property Carbon $updated_at */ From f5dc0ecb46a1663da9755cb1c0b1e15f6c9bbea0 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Wed, 24 Nov 2021 16:02:42 -0300 Subject: [PATCH 05/42] add disabled input text --- .../components/filters/input-text.blade.php | 48 +++++++++++++------ .../frameworks/tailwind/filter.blade.php | 4 +- .../frameworks/tailwind/table-base.blade.php | 1 + .../views/components/inline-filters.blade.php | 4 +- resources/views/components/table.blade.php | 1 + src/Traits/Filter.php | 9 +++- 6 files changed, 50 insertions(+), 17 deletions(-) diff --git a/resources/views/components/filters/input-text.blade.php b/resources/views/components/filters/input-text.blade.php index 5c804a41..23f476fe 100644 --- a/resources/views/components/filters/input-text.blade.php +++ b/resources/views/components/filters/input-text.blade.php @@ -1,5 +1,6 @@ @props([ 'theme' => '', + 'enabledFilters' => [], 'column' => null, 'inline' => null, 'inputText' => null @@ -16,20 +17,32 @@
@@ -37,13 +50,20 @@ class="power_grid {{ $theme->selectClass }} {{ data_get($column, 'headerClass')
- + @else + + @endif
diff --git a/resources/views/components/frameworks/tailwind/filter.blade.php b/resources/views/components/frameworks/tailwind/filter.blade.php index 7fe7208f..9fe40f51 100644 --- a/resources/views/components/frameworks/tailwind/filter.blade.php +++ b/resources/views/components/frameworks/tailwind/filter.blade.php @@ -3,7 +3,8 @@ 'checkbox' => null, 'columns' => null, 'actions' => null, - 'theme' => null + 'theme' => null, + 'enabledFilters' => null, ])
@@ -54,6 +55,7 @@ classAttr="w-full" @foreach(data_get($makeFilters, 'input_text', []) as $field => $inputText)
diff --git a/resources/views/components/frameworks/tailwind/table-base.blade.php b/resources/views/components/frameworks/tailwind/table-base.blade.php index f9375d7b..bd0ca731 100644 --- a/resources/views/components/frameworks/tailwind/table-base.blade.php +++ b/resources/views/components/frameworks/tailwind/table-base.blade.php @@ -11,6 +11,7 @@
diff --git a/resources/views/components/inline-filters.blade.php b/resources/views/components/inline-filters.blade.php index 5b0f5139..2d7c0356 100644 --- a/resources/views/components/inline-filters.blade.php +++ b/resources/views/components/inline-filters.blade.php @@ -3,7 +3,8 @@ 'checkbox' => null, 'columns' => null, 'actions' => null, - 'theme' => null + 'theme' => null, + 'enabledFilters' => null, ])
@if(config('livewire-powergrid.filter') === 'inline') @@ -62,6 +63,7 @@ @if(data_get($inputText, 'field') === $column->field) diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php index 8be1212e..bde7daab 100644 --- a/resources/views/components/table.blade.php +++ b/resources/views/components/table.blade.php @@ -34,6 +34,7 @@ :actions="$actions" :columns="$columns" :theme="$theme" + :enabledFilters="$enabledFilters" /> @if(is_null($data) || count($data) === 0) diff --git a/src/Traits/Filter.php b/src/Traits/Filter.php index bb2812a1..01a1c20c 100644 --- a/src/Traits/Filter.php +++ b/src/Traits/Filter.php @@ -2,7 +2,7 @@ namespace PowerComponents\LivewirePowerGrid\Traits; -use Illuminate\Support\Collection; +use Illuminate\Support\{Arr, Collection}; use PowerComponents\LivewirePowerGrid\Column; trait Filter @@ -194,6 +194,13 @@ public function filterInputTextOptions(string $field, string $value, string $lab $this->resetPage(); + if (in_array($value, ['is_empty', 'is_not_empty', 'is_null', 'is_not_null', 'is_blank', 'is_not_blank'])) { + $this->enabledFilters[$field]['disabled'] = true; + $this->filters['input_text'][$field] = null; + } else { + $this->enabledFilters[$field]['disabled'] = false; + } + if ($value == '') { $this->clearFilter($field); } From 8c8e812327b31a85cb2e6f4166993f5eb28cb80c Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Wed, 24 Nov 2021 20:04:12 -0300 Subject: [PATCH 06/42] wip --- resources/stubs/PowerGridDemoTable.stub | 8 +++++--- resources/stubs/table.fillable.stub | 2 ++ resources/stubs/table.model.stub | 2 ++ src/PowerGridComponent.php | 14 +++++++------- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/resources/stubs/PowerGridDemoTable.stub b/resources/stubs/PowerGridDemoTable.stub index 1727ca31..9fa9914d 100644 --- a/resources/stubs/PowerGridDemoTable.stub +++ b/resources/stubs/PowerGridDemoTable.stub @@ -28,12 +28,14 @@ class PowerGridDemoTable extends PowerGridComponent { use ActionButton; - protected $demoUsers = null; //Demo users. Should be removed in a real project. + protected $demoUsers = null; // Demo users. Should be removed in a real project. protected function getListeners() { - $this->listeners[] = 'alertEvent'; - return $this->listeners; + return array_merge([ + 'alertEvent', + $this->listeners + ]); } public function alertEvent($data) diff --git a/resources/stubs/table.fillable.stub b/resources/stubs/table.fillable.stub index b70cb980..44907ad6 100644 --- a/resources/stubs/table.fillable.stub +++ b/resources/stubs/table.fillable.stub @@ -17,6 +17,8 @@ class {{ componentName }} extends PowerGridComponent { use ActionButton; + public bool $showUpdateMessages = true; + /* |-------------------------------------------------------------------------- | Features Setup diff --git a/resources/stubs/table.model.stub b/resources/stubs/table.model.stub index bf6941af..e7a66353 100644 --- a/resources/stubs/table.model.stub +++ b/resources/stubs/table.model.stub @@ -17,6 +17,8 @@ class {{ componentName }} extends PowerGridComponent { use ActionButton; + public bool $showUpdateMessages = true; + /* |-------------------------------------------------------------------------- | Features Setup diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index 4dd4cbf4..da4409b3 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -59,7 +59,7 @@ class PowerGridComponent extends Component public bool $ignoreTablePrefix = false; - public bool $showDefaultMessage = false; + public bool $showUpdateMessages = false; /** * @var string[] $listeners @@ -269,18 +269,18 @@ public function eventInputChanged(array $data): void { $update = $this->update($data); - if (!$update) { - session()->flash('error', $this->updateMessages('error', data_get($data, 'field'))); + $this->fillData(); + if (!$this->showUpdateMessages) { return; } - session()->flash('success', $this->updateMessages('success', data_get($data, 'field'))); - if (!is_array($this->datasource)) { + if (!$update) { + session()->flash('error', $this->updateMessages('error', data_get($data, 'field'))); + return; } - - $this->fillData(); + session()->flash('success', $this->updateMessages('success', data_get($data, 'field'))); } /** From 6b244d93281a8004f2ac8f2db118e819fcae72b6 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 22:58:04 -0300 Subject: [PATCH 07/42] feature: added "docker-compose.yml" for orchestration of all database containers. --- docker-compose.yml | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..9c52bfc7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +version: '3' +services: + mysql: + container_name: 'mysql_test' + image: 'mysql/mysql-server:8.0' + ports: + - '3307:3306' + command: '--default-authentication-plugin=mysql_native_password' + environment: + MYSQL_ROOT_PASSWORD: 'password' + MYSQL_ROOT_HOST: "%" + MYSQL_DATABASE: 'powergridtest' + MYSQL_USER: 'powergrid' + MYSQL_PASSWORD: 'password' + MYSQL_ALLOW_EMPTY_PASSWORD: 1 + networks: + - sail + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-ppassword" ] + retries: 3 + timeout: 5s + pgsql: + container_name: 'pgsql_test' + image: 'postgres:13' + ports: + - '5433:5432' + environment: + PGPASSWORD: 'password' + POSTGRES_DB: 'powergridtest' + POSTGRES_USER: 'postgres' + POSTGRES_PASSWORD: 'password' + networks: + - sail + healthcheck: + test: [ "CMD", "pg_isready", "-q", "-d", "powergridtest", "-U", "postgres" ] + retries: 3 + timeout: 5s + sqlsrv: + container_name: 'sqlsrv_test' + image: 'mcr.microsoft.com/mssql/server:2017-latest' + ports: + - '1434:1433' + environment: + ACCEPT_EULA: 'Y' + SA_PASSWORD: 'yourStrong(!)Password' + networks: + - sail + healthcheck: + test: [ "CMD", "/opt/mssql-tools/bin/sqlcmd", "-S", "localhost", "-U", "sa", "-P", "yourStrong(!)Password", "-Q", "select 1" ] + retries: 3 + timeout: 5s +networks: + sail: + driver: bridge From 368a27a1155c6c76681574bf7a4c87d446fa24f0 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 23:03:59 -0300 Subject: [PATCH 08/42] fix: remove duplicated entry for php-cs-fixer --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9a0b761d..3a78034a 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,7 @@ "php": "^7.4.1 | ^8.0 | ^8.1", "livewire/livewire": "^2.4", "box/spout": "^3", - "doctrine/dbal": "^3.1", - "friendsofphp/php-cs-fixer": "^3.2" + "doctrine/dbal": "^3.1" }, "scripts": { "stan": "phpstan analyse --ansi --memory-limit=-1", From e4c83115de26a17d13018849b143a711174354cb Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 23:10:28 -0300 Subject: [PATCH 09/42] feature: added support for all databases supported by laravel to the functionality for sorting alphanumeric values. --- src/Helpers/SqlSupport.php | 84 ++++++++++++++++++++++++++++++++++++++ src/PowerGridComponent.php | 14 +++++-- 2 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/Helpers/SqlSupport.php diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php new file mode 100644 index 00000000..16428d0d --- /dev/null +++ b/src/Helpers/SqlSupport.php @@ -0,0 +1,84 @@ + [ + '*' => "CAST($sortField AS INTEGER)", + ], + 'mysql' => [ + '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", + ], + 'pgsql' => [ + '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS BANANA)", + ], + 'sqlsrv' => [ + '*' => "CAST(SUBSTRING($sortField, PATINDEX('%[a-z]%', $sortField), LEN($sortField)-PATINDEX('%[a-z]%', $sortField)) AS INT)", + ], + ]; + + return $sqlByDriver[$driverName][$driverVersion] ?? $sortField; + } + + /** + * @param string $sortFieldType + * @return bool + */ + public static function isValidSortFieldType(string $sortFieldType): bool + { + return in_array($sortFieldType, self::$sortStringNumberTypes); + } + + /** + * @param string $sortField + * @return string + */ + public static function getSortFieldType(string $sortField): string + { + $data = explode('.', $sortField); + + return Schema::getConnection() + ->getDoctrineColumn($data[0], $data[1]) + ->getType() + ->getName(); + } +} diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index 4dd4cbf4..d7802261 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -9,7 +9,7 @@ use Illuminate\Pagination\{AbstractPaginator, LengthAwarePaginator}; use Illuminate\Support\{Collection as BaseCollection, Str}; use Livewire\{Component, WithPagination}; -use PowerComponents\LivewirePowerGrid\Helpers\{Collection, Model}; +use PowerComponents\LivewirePowerGrid\Helpers\{Collection, Model, SqlSupport}; use PowerComponents\LivewirePowerGrid\Themes\ThemeBase; use PowerComponents\LivewirePowerGrid\Traits\{BatchableExport, Checkbox, Exportable, Filter, WithSorting}; @@ -174,7 +174,7 @@ public function mount(): void */ public function showPerPage(int $perPage = 10): PowerGridComponent { - if (\Str::contains((string) $perPage, $this->perPageValues)) { + if (Str::contains((string) $perPage, $this->perPageValues)) { $this->perPageInput = true; $this->perPage = $perPage; } @@ -367,6 +367,8 @@ public function fillData() $sortField = $this->currentTable . '.' . $this->sortField; } + $sortFieldType = SqlSupport::getSortFieldType($sortField); + /** @var Builder $results */ $results = $this->resolveModel($datasource) ->where(function (Builder $query) { @@ -379,8 +381,8 @@ public function fillData() ->filter(); }); - if ($this->withSortStringNumber) { - $results->orderByRaw("$sortField+0 $this->sortDirection"); + if ($this->withSortStringNumber && SqlSupport::isValidSortFieldType($sortFieldType)) { + $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); } $results = $results->orderBy($sortField, $this->sortDirection); @@ -419,6 +421,10 @@ private function transform($results) }); } + /** + * @param string $field + * @throws Exception + */ public function toggleColumn(string $field): void { $this->columns = collect($this->columns)->map(function ($column) use ($field) { From 80f0532a65fe66579eef58788ee3101c8aa357eb Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 23:25:08 -0300 Subject: [PATCH 10/42] style: apply php-cs-fixer --- src/Helpers/SqlSupport.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 16428d0d..4b9a03d8 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -30,6 +30,7 @@ public static function like(): string public static function sortStringAsNumber(string $sortField): string { $driverName = DB::getDriverName(); + return self::getSortSqlByDriver($sortField, $driverName); } From ea1c76ebc28b8c642faeb1f557b12252d0514866 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Wed, 24 Nov 2021 23:40:27 -0300 Subject: [PATCH 11/42] fix: changed type of cast value on database --- src/Helpers/SqlSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 4b9a03d8..e17d858a 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -50,7 +50,7 @@ private static function getSortSqlByDriver(string $sortField, string $driverName '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", ], 'pgsql' => [ - '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS BANANA)", + '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER)", ], 'sqlsrv' => [ '*' => "CAST(SUBSTRING($sortField, PATINDEX('%[a-z]%', $sortField), LEN($sortField)-PATINDEX('%[a-z]%', $sortField)) AS INT)", From 94939213da7ffa4d70e3e80e9e4d8f896b92f191 Mon Sep 17 00:00:00 2001 From: Renier Date: Thu, 25 Nov 2021 14:47:05 +0800 Subject: [PATCH 12/42] Update WithSorting.php Add parameter $direction to customize sort by order --- src/Traits/WithSorting.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Traits/WithSorting.php b/src/Traits/WithSorting.php index 8be47345..7dfca9ee 100644 --- a/src/Traits/WithSorting.php +++ b/src/Traits/WithSorting.php @@ -12,23 +12,19 @@ trait WithSorting public bool $withSortStringNumber = false; - public function sortBy(string $field): void + public function sortBy(string $field, string $direction = 'asc'): void { - $this->sortDirection = $this->sortField === $field - ? $this->reverseSort() - : 'asc'; + $this->sortDirection = $this->sortField === $field ? $this->reverseSort() : $direction; $this->sortField = $field; } public function reverseSort(): string { - return $this->sortDirection === 'asc' - ? 'desc' - : 'asc'; + return $this->sortDirection === 'asc'? 'desc' : 'asc'; } - public function applySorting(Collection $query): Collection + public function applySorting($query) { if (is_a($query, Collection::class)) { return $query->sortBy($this->sortField, SORT_REGULAR, !(($this->sortDirection === 'asc'))); From 96548a95302268197f3565b03de3c7c3241dc10a Mon Sep 17 00:00:00 2001 From: Renier Date: Thu, 25 Nov 2021 14:48:36 +0800 Subject: [PATCH 13/42] Update WithSorting.php return type declaration in applySorting --- src/Traits/WithSorting.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Traits/WithSorting.php b/src/Traits/WithSorting.php index 7dfca9ee..16b9c14d 100644 --- a/src/Traits/WithSorting.php +++ b/src/Traits/WithSorting.php @@ -24,7 +24,7 @@ public function reverseSort(): string return $this->sortDirection === 'asc'? 'desc' : 'asc'; } - public function applySorting($query) + public function applySorting(Collection $query): Collection { if (is_a($query, Collection::class)) { return $query->sortBy($this->sortField, SORT_REGULAR, !(($this->sortDirection === 'asc'))); From 595fbebe501a74ec61f77a5aeb76162d8502671d Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 11:34:22 -0300 Subject: [PATCH 14/42] add langs to empty null filter --- resources/lang/ca/datatable.php | 6 ++++++ resources/lang/de/datatable.php | 6 ++++++ resources/lang/es/datatable.php | 6 ++++++ resources/lang/fa/datatable.php | 10 ++++++++-- resources/lang/id/datatable.php | 10 ++++++++++ resources/lang/it/datatable.php | 6 ++++++ resources/lang/ms_MY/datatable.php | 6 ++++++ resources/lang/pt_BR/datatable.php | 6 ++++++ resources/lang/tr/datatable.php | 6 ++++++ resources/lang/yr/datatable.php | 6 ++++++ resources/views/components/editable.blade.php | 1 - src/Traits/WithSorting.php | 2 +- 12 files changed, 67 insertions(+), 4 deletions(-) diff --git a/resources/lang/ca/datatable.php b/resources/lang/ca/datatable.php index 30416d77..ac5903fa 100644 --- a/resources/lang/ca/datatable.php +++ b/resources/lang/ca/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'No té contingut', 'starts_with' => 'Comença per', 'ends_with' => 'Acaba en', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => 'Espereu, si us plau!', diff --git a/resources/lang/de/datatable.php b/resources/lang/de/datatable.php index 0bbca087..02311a95 100644 --- a/resources/lang/de/datatable.php +++ b/resources/lang/de/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'enthält nicht', 'starts_with' => 'beginnt mit', 'ends_with' => 'endet with', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => 'Warten Sie mal!', diff --git a/resources/lang/es/datatable.php b/resources/lang/es/datatable.php index bb0b7bd0..a5adf3ee 100644 --- a/resources/lang/es/datatable.php +++ b/resources/lang/es/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'No contiene', 'starts_with' => 'Comienza por', 'ends_with' => 'Termina en', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => '¡Espere por favor!', diff --git a/resources/lang/fa/datatable.php b/resources/lang/fa/datatable.php index 574949ad..b91de1fe 100644 --- a/resources/lang/fa/datatable.php +++ b/resources/lang/fa/datatable.php @@ -44,9 +44,15 @@ 'contains_not' => 'شامل نباشد', 'starts_with' => 'شروع شود با', 'ends_with' => 'خاتمه یابد با', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ - 'exporting' => 'لطفا صبر کنید!', - 'completed' => 'صادرات به پایان رسید! فایل های شما آماده بارگیری هستند', + 'exporting' => 'Please wait!', + 'completed' => 'Export completed! Your files are ready for download', ], ]; diff --git a/resources/lang/id/datatable.php b/resources/lang/id/datatable.php index 9fb07391..cabf7ee2 100644 --- a/resources/lang/id/datatable.php +++ b/resources/lang/id/datatable.php @@ -41,5 +41,15 @@ 'contains_not' => 'Tidak terkait', 'starts_with' => 'Dimulai dengan', 'ends_with' => 'Diakhiri dengan', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', + ], + 'export' => [ + 'exporting' => 'Please wait!', + 'completed' => 'Export completed! Your files are ready for download', ], ]; diff --git a/resources/lang/it/datatable.php b/resources/lang/it/datatable.php index 07254818..3fe519b9 100644 --- a/resources/lang/it/datatable.php +++ b/resources/lang/it/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'Non contiene', 'starts_with' => 'Inizia con', 'ends_with' => 'Termina con', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => 'Attendere prego!', diff --git a/resources/lang/ms_MY/datatable.php b/resources/lang/ms_MY/datatable.php index 4cb5986d..bab2a12a 100644 --- a/resources/lang/ms_MY/datatable.php +++ b/resources/lang/ms_MY/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'Tidak mengandungi', 'starts_with' => 'Bermula dengan', 'ends_with' => 'Berakhir dengan', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => 'Sila tunggu!', diff --git a/resources/lang/pt_BR/datatable.php b/resources/lang/pt_BR/datatable.php index a0754350..42bec5b2 100644 --- a/resources/lang/pt_BR/datatable.php +++ b/resources/lang/pt_BR/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'Não contém', 'starts_with' => 'Começa com', 'ends_with' => 'Termina com', + 'is_null' => 'É nulo', + 'is_not_null' => 'Não é núlo', + 'is_blank' => 'Está em branco', + 'is_not_blank' => 'Não está em branco', + 'is_empty' => 'Não está preenchido', + 'is_not_empty' => 'Está preenchido', ], 'export' => [ 'exporting' => 'Por favor, aguarde!', diff --git a/resources/lang/tr/datatable.php b/resources/lang/tr/datatable.php index 82160882..13c5f404 100644 --- a/resources/lang/tr/datatable.php +++ b/resources/lang/tr/datatable.php @@ -41,5 +41,11 @@ 'contains_not' => 'İçermeyen', 'starts_with' => 'Başlayan', 'ends_with' => 'Biten', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], ]; diff --git a/resources/lang/yr/datatable.php b/resources/lang/yr/datatable.php index c4845f85..fc461cc9 100644 --- a/resources/lang/yr/datatable.php +++ b/resources/lang/yr/datatable.php @@ -41,6 +41,12 @@ 'contains_not' => 'Ko Si Ninu', 'starts_with' => 'Bere Pelu', 'ends_with' => 'Pari Pelu', + 'is_null' => 'is_null', + 'is_not_null' => 'is_not_null', + 'is_blank' => 'is_blank', + 'is_not_blank' => 'is_not_blank', + 'is_empty' => 'is_empty', + 'is_not_empty' => 'is_not_empty', ], 'export' => [ 'exporting' => 'jọwọ duro!', diff --git a/resources/views/components/editable.blade.php b/resources/views/components/editable.blade.php index 3cd1af4d..dd06a60b 100644 --- a/resources/views/components/editable.blade.php +++ b/resources/views/components/editable.blade.php @@ -4,7 +4,6 @@ 'field' => null, 'theme' => null ]) -
Date: Thu, 25 Nov 2021 11:52:31 -0300 Subject: [PATCH 17/42] update ci --- .github/workflows/php.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 737c34ad..7090ed28 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,17 +2,18 @@ name: Powergrid Tests on: push: - branches: - - main + branches: + - main + - dev pull_request: types: [ ready_for_review, synchronize, opened ] - branches: [ main ] + branches: [ main, dev ] jobs: build: runs-on: ubuntu-latest - + services: mysql: image: mysql:5.7 @@ -32,7 +33,7 @@ jobs: ports: - 5433:5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 - + strategy: matrix: php: [ 7.4, 8.0 ] @@ -71,11 +72,11 @@ jobs: - name: Cs Fixer run: ./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation - + - name: Testing with SQLite run: | ./vendor/bin/pest --configuration phpunit.sqlite.xml - + - name: Testing with MySQL run: | ./vendor/bin/pest --configuration phpunit.mysql.xml @@ -85,4 +86,4 @@ jobs: ./vendor/bin/pest --configuration phpunit.pgsql.xml - name: Larastan - run: ./vendor/bin/phpstan analyse --ansi --memory-limit=-1 \ No newline at end of file + run: ./vendor/bin/phpstan analyse --ansi --memory-limit=-1 From 5ddb9ac2852dda72b91279c5475c19af89061056 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 11:52:58 -0300 Subject: [PATCH 18/42] update ci --- .github/workflows/php.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 7090ed28..f23a9062 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,13 +2,12 @@ name: Powergrid Tests on: push: - branches: - - main - - dev + branches: + - main pull_request: types: [ ready_for_review, synchronize, opened ] - branches: [ main, dev ] + branches: [ main ] jobs: build: From 8187e53fd9107f2b9fced06c2540a4ce4454bb59 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 14:00:36 -0300 Subject: [PATCH 19/42] fix getsortFieldType --- resources/views/components/editable.blade.php | 2 +- src/Helpers/SqlSupport.php | 14 +++++++++++--- src/PowerGridComponent.php | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/resources/views/components/editable.blade.php b/resources/views/components/editable.blade.php index dd06a60b..d993f9b7 100644 --- a/resources/views/components/editable.blade.php +++ b/resources/views/components/editable.blade.php @@ -7,7 +7,7 @@
diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index d2482e6b..b9871076 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -2,7 +2,7 @@ namespace PowerComponents\LivewirePowerGrid\Helpers; -use Illuminate\Support\Facades\{DB, Schema}; +use Illuminate\Support\Facades\{DB, Log, Schema}; class SqlSupport { @@ -66,8 +66,11 @@ private static function getSortSqlByDriver(string $sortField, string $driverName * @param string $sortFieldType * @return bool */ - public static function isValidSortFieldType(string $sortFieldType): bool + public static function isValidSortFieldType(?string $sortFieldType): bool { + if (is_null($sortFieldType)) { + return false; + } return in_array($sortFieldType, self::$sortStringNumberTypes); } @@ -75,10 +78,15 @@ public static function isValidSortFieldType(string $sortFieldType): bool * @param string $sortField * @return string */ - public static function getSortFieldType(string $sortField): string + public static function getSortFieldType(string $sortField): ?string { $data = explode('.', $sortField); + if (!isset($data[1]) || !Schema::hasColumn($data[0], $data[1])) { + + return null; + } + return Schema::getConnection() ->getDoctrineColumn($data[0], $data[1]) ->getType() diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index 166b8cc2..ce876bed 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -382,6 +382,7 @@ public function fillData() }); if ($this->withSortStringNumber && SqlSupport::isValidSortFieldType($sortFieldType)) { + $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); } From 4cd8c7bc5c525f12afa7bd909a5577ccc711a242 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 14:32:11 -0300 Subject: [PATCH 20/42] add throw column not found --- src/Helpers/SqlSupport.php | 10 +++++++--- src/PowerGridComponent.php | 1 - 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index b9871076..f1834eee 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -71,22 +71,26 @@ public static function isValidSortFieldType(?string $sortFieldType): bool if (is_null($sortFieldType)) { return false; } + return in_array($sortFieldType, self::$sortStringNumberTypes); } /** * @param string $sortField * @return string + * @throws \Exception */ public static function getSortFieldType(string $sortField): ?string { $data = explode('.', $sortField); - - if (!isset($data[1]) || !Schema::hasColumn($data[0], $data[1])) { - + if (!isset($data[1])) { return null; } + if (!Schema::hasColumn($data[0], $data[1])) { + throw new \Exception("There is no column with name '$data[1]' on table '$data[0]'. Please see: https://livewire-powergrid.docsforge.com/main/include-columns/#fieldstring-field-string-datafield"); + } + return Schema::getConnection() ->getDoctrineColumn($data[0], $data[1]) ->getType() diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index ce876bed..166b8cc2 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -382,7 +382,6 @@ public function fillData() }); if ($this->withSortStringNumber && SqlSupport::isValidSortFieldType($sortFieldType)) { - $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); } From 1c7482b0c216e9581f8350dc6fd00a9e2b296d8f Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 17:52:29 -0300 Subject: [PATCH 21/42] wip --- resources/stubs/PowerGridDemoTable.stub | 6 ++---- resources/stubs/table.collection.stub | 2 +- resources/stubs/table.model.stub | 2 +- src/Commands/CreateCommand.php | 8 +++++--- src/Helpers/Collection.php | 24 +++++++++++++++--------- 5 files changed, 24 insertions(+), 18 deletions(-) diff --git a/resources/stubs/PowerGridDemoTable.stub b/resources/stubs/PowerGridDemoTable.stub index 9fa9914d..63d42e2e 100644 --- a/resources/stubs/PowerGridDemoTable.stub +++ b/resources/stubs/PowerGridDemoTable.stub @@ -32,10 +32,8 @@ class PowerGridDemoTable extends PowerGridComponent protected function getListeners() { - return array_merge([ - 'alertEvent', - $this->listeners - ]); + $this->listeners[] = 'alertEvent'; + return $this->listeners; } public function alertEvent($data) diff --git a/resources/stubs/table.collection.stub b/resources/stubs/table.collection.stub index 428be5f0..c4102e7a 100644 --- a/resources/stubs/table.collection.stub +++ b/resources/stubs/table.collection.stub @@ -98,7 +98,7 @@ class {{ componentName }} extends PowerGridComponent Column::add() ->title(__('Created At')) - ->field('created_at_formatted') + ->field('created_at_formatted', 'created_at') ->makeInputDatePicker('created_at'), ]; } diff --git a/resources/stubs/table.model.stub b/resources/stubs/table.model.stub index e7a66353..206299f7 100644 --- a/resources/stubs/table.model.stub +++ b/resources/stubs/table.model.stub @@ -108,7 +108,7 @@ class {{ componentName }} extends PowerGridComponent Column::add() ->title(__('Created at')) - ->field('created_at_formatted') + ->field('created_at_formatted', 'created_at') ->makeInputDatePicker('created_at') ->searchable() ]; diff --git a/src/Commands/CreateCommand.php b/src/Commands/CreateCommand.php index 04648cfe..e8cc7735 100644 --- a/src/Commands/CreateCommand.php +++ b/src/Commands/CreateCommand.php @@ -148,7 +148,7 @@ public function handle(): void $stub = str_replace('{{ subFolder }}', $subFolder, $stub); $stub = str_replace('{{ componentName }}', $componentName, $stub); - if ($creationModel === 'm') { + if (strtolower($creationModel) === 'm') { $stub = str_replace('{{ modelName }}', $modelName, $stub); $stub = str_replace('{{ modelLastName }}', $modelLastName, $stub); $stub = str_replace('{{ modelLowerCase }}', Str::lower($modelLastName), $stub); @@ -224,16 +224,18 @@ private function createFromFillable(string $modelName, string $modelLastName): s $title = Str::of($field)->replace('_', ' ')->upper(); + if (in_array($column->getType()->getName(), ['datetime', 'date'])) { + $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '_formatted\', \'' . $field . '\')' . "\n" . ' ->searchable()' . "\n" . ' ->sortable()' . "\n" . ' ->makeInputDatePicker(\'' . $field . '\'),' . "\n\n"; + } + if ($column->getType()->getName() === 'datetime') { $datasource .= "\n" . ' ->addColumn(\'' . $field . '_formatted\', function(' . $modelLastName . ' $model) { ' . "\n" . ' return Carbon::parse($model->' . $field . ')->format(\'d/m/Y H:i:s\');' . "\n" . ' })'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '_formatted\')' . "\n" . ' ->searchable()' . "\n" . ' ->sortable()' . "\n" . ' ->makeInputDatePicker(\'' . $field . '\'),' . "\n\n"; continue; } if ($column->getType()->getName() === 'date') { $datasource .= "\n" . ' ->addColumn(\'' . $field . '_formatted\', function(' . $modelLastName . ' $model) { ' . "\n" . ' return Carbon::parse($model->' . $field . ')->format(\'d/m/Y\');' . "\n" . ' })'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '_formatted\')' . "\n" . ' ->searchable()' . "\n" . ' ->sortable()' . "\n" . ' ->makeInputDatePicker(\'' . $field . '\'),' . "\n\n"; continue; } diff --git a/src/Helpers/Collection.php b/src/Helpers/Collection.php index 3b89de0a..007e8c53 100644 --- a/src/Helpers/Collection.php +++ b/src/Helpers/Collection.php @@ -4,7 +4,7 @@ use Illuminate\Container\Container; use Illuminate\Pagination\{LengthAwarePaginator, Paginator}; -use Illuminate\Support\{Carbon, Collection as BaseCollection, Str}; +use Illuminate\Support\{Carbon, Collection as BaseCollection, Facades\Schema, Str}; use PowerComponents\LivewirePowerGrid\Services\Contracts\CollectionFilterInterface; class Collection implements CollectionFilterInterface @@ -198,10 +198,10 @@ private function validateInputTextOptions(string $field): bool /** * @param string $field - * @param string $value + * @param string|null $value * @return void */ - public function filterInputText(string $field, string $value): void + public function filterInputText(string $field, ?string $value): void { $textFieldOperator = ($this->validateInputTextOptions($field) ? strtolower($this->filters['input_text_options'][$field]) : 'contains'); @@ -378,14 +378,20 @@ public function filterContains(): Collection $row = (object) $row; foreach ($this->columns as $column) { - $field = $column->field; + if ($column->searchable) { + if (filled($column->dataField)) { + $field = $column->dataField; + } else { + $field = $column->field; + } - try { - if (Str::contains(strtolower($row->{$field}), strtolower($this->search))) { - return false !== stristr($row->{$field}, strtolower($this->search)); + try { + if (Str::contains(strtolower($row->{$field}), strtolower($this->search))) { + return false !== stristr($row->{$field}, strtolower($this->search)); + } + } catch (\Exception $exception) { + throw new \Exception($exception); } - } catch (\Exception $exception) { - throw new \Exception($exception); } } From 9ece04b2a5d347e7a22820f0b155d24f92897e46 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 17:56:44 -0300 Subject: [PATCH 22/42] fix stan warning --- src/Helpers/Collection.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Helpers/Collection.php b/src/Helpers/Collection.php index 007e8c53..e1337f3c 100644 --- a/src/Helpers/Collection.php +++ b/src/Helpers/Collection.php @@ -218,7 +218,7 @@ public function filterInputText(string $field, ?string $value): void $this->query = $this->query->filter(function ($row) use ($field, $value) { $row = (object) $row; - return Str::startsWith(Str::lower($row->{$field}), Str::lower($value)); + return Str::startsWith(Str::lower($row->{$field}), Str::lower((string) $value)); }); break; @@ -226,7 +226,7 @@ public function filterInputText(string $field, ?string $value): void $this->query = $this->query->filter(function ($row) use ($field, $value) { $row = (object) $row; - return Str::endsWith(Str::lower($row->{$field}), Str::lower($value)); + return Str::endsWith(Str::lower($row->{$field}), Str::lower((string) $value)); }); break; @@ -234,7 +234,7 @@ public function filterInputText(string $field, ?string $value): void $this->query = $this->query->filter(function ($row) use ($field, $value) { $row = (object) $row; - return false !== stristr($row->{$field}, strtolower($value)); + return false !== stristr($row->{$field}, strtolower((string) $value)); }); break; @@ -243,7 +243,7 @@ public function filterInputText(string $field, ?string $value): void $this->query = $this->query->filter(function ($row) use ($field, $value) { $row = (object) $row; - return !Str::Contains(Str::lower($row->{$field}), Str::lower($value)); + return !Str::Contains(Str::lower($row->{$field}), Str::lower((string) $value)); }); break; From 7a923a901a9bd0d3bbfa81088b5626cf9030471a Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 19:12:54 -0300 Subject: [PATCH 23/42] fix inputText --- resources/views/components/editable.blade.php | 26 ++++++++++++++++--- .../components/filters/input-text.blade.php | 17 +++++++----- resources/views/components/row.blade.php | 1 + src/Column.php | 24 ++++++++++++----- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/resources/views/components/editable.blade.php b/resources/views/components/editable.blade.php index d993f9b7..ff0df759 100644 --- a/resources/views/components/editable.blade.php +++ b/resources/views/components/editable.blade.php @@ -1,15 +1,35 @@ +@inject('strClass', '\Illuminate\Support\Str') @props([ 'primaryKey' => null, 'row' => null, 'field' => null, - 'theme' => null + 'theme' => null, + 'currentTable' => null, ]) +@php + $table = $currentTable; + $currentField = $field; + + if (str_contains($currentField, '.')) { + $data = $strClass::of($field)->explode('.'); + $table = $data->get(0); + $field = $data->get(1); + + if ($table === $currentTable) { + $content = addslashes($row->{$field}); + } else { + $content = addslashes($row->{$table}->{$field}); + } + } else { + $content = addslashes($row->{$field}); + } +@endphp
null ])
+ @php + $field = data_get($inputText, 'dataField') ?? data_get($inputText, 'field'); + @endphp @if(filled($inputText))
@if(!$inline) @@ -17,8 +20,8 @@ @else diff --git a/resources/views/components/row.blade.php b/resources/views/components/row.blade.php index 927ba455..af2c9e43 100644 --- a/resources/views/components/row.blade.php +++ b/resources/views/components/row.blade.php @@ -14,6 +14,7 @@ diff --git a/src/Column.php b/src/Column.php index ea4cfe52..053c6225 100644 --- a/src/Column.php +++ b/src/Column.php @@ -118,7 +118,9 @@ public function sortable(): Column public function field(string $field, string $dataField = ''): Column { $this->field = $field; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } @@ -216,7 +218,9 @@ public function makeInputDatePicker(string $dataField, array $settings = [], str $this->inputs['date_picker']['enabled'] = true; $this->inputs['date_picker']['class'] = $classAttr; $this->inputs['date_picker']['config'] = $settings; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } @@ -231,7 +235,9 @@ public function makeInputDatePicker(string $dataField, array $settings = [], str public function editOnClick(bool $hasPermission = true, string $dataField = ''): Column { $this->editable = $hasPermission; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } @@ -266,7 +272,9 @@ public function makeInputRange(string $dataField = '', string $thousands = '', s $this->inputs['number']['enabled'] = true; $this->inputs['number']['decimal'] = $decimal; $this->inputs['number']['thousands'] = $thousands; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } @@ -278,7 +286,9 @@ public function makeInputRange(string $dataField = '', string $thousands = '', s public function makeInputText(string $dataField = ''): Column { $this->inputs['input_text']['enabled'] = true; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } @@ -312,7 +322,9 @@ public function makeBooleanFilter(string $dataField = '', string $trueLabel = 'Y $this->inputs['boolean_filter']['false_label'] = $falseLabel; $this->inputs['boolean_filter']['class'] = $settings['class'] ?? ''; $this->inputs['boolean_filter']['live-search'] = $settings['live-search'] ?? true; - $this->dataField = $dataField; + if (filled($dataField)) { + $this->dataField = $dataField; + } return $this; } From eacff73d23b8f007fe80697111fddd0045b5116c Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Thu, 25 Nov 2021 23:23:48 +0100 Subject: [PATCH 24/42] Stubs passing static analysis --- resources/stubs/table.collection.stub | 24 ++++++---- resources/stubs/table.fillable.stub | 60 +++++++++++++++++------- resources/stubs/table.model.stub | 66 +++++++++++++++++++-------- src/Commands/CreateCommand.php | 10 ++-- 4 files changed, 111 insertions(+), 49 deletions(-) diff --git a/resources/stubs/table.collection.stub b/resources/stubs/table.collection.stub index c4102e7a..c5d244bd 100644 --- a/resources/stubs/table.collection.stub +++ b/resources/stubs/table.collection.stub @@ -10,7 +10,7 @@ use PowerComponents\LivewirePowerGrid\PowerGridEloquent; use PowerComponents\LivewirePowerGrid\PowerGridComponent; use PowerComponents\LivewirePowerGrid\Traits\ActionButton; -class {{ componentName }} extends PowerGridComponent +final class {{ componentName }} extends PowerGridComponent { use ActionButton; @@ -21,7 +21,7 @@ class {{ componentName }} extends PowerGridComponent | Provides data to your Table using a Model or Collection | */ - public function datasource(): Collection + public function datasource(): ?Collection { return collect([ ['id' => 1, 'name' => 'Name 1', 'price' => 1.58, 'created_at' => now(),], @@ -39,7 +39,7 @@ class {{ componentName }} extends PowerGridComponent | Configure here relationships to be used by the Search and Table Filters. | */ - public function setUp() + public function setUp(): void { $this->showCheckBox() ->showPerPage() @@ -55,7 +55,7 @@ class {{ componentName }} extends PowerGridComponent | You can pass a closure to transform/modify the data. | */ - public function addColumns(): PowerGridEloquent + public function addColumns(): ?PowerGridEloquent { return PowerGrid::eloquent() ->addColumn('id') @@ -73,32 +73,38 @@ class {{ componentName }} extends PowerGridComponent | Include the columns added columns, making them visible on the Table. | Each column can be configured with properties, filters, actions... | + */ + /** + * PowerGrid Columns. + * + * @return array + */ public function columns(): array { return [ Column::add() - ->title(__('ID')) + ->title('ID') ->field('id') ->searchable() ->sortable(), Column::add() - ->title(__('Name')) + ->title('Name') ->field('name') ->searchable() ->makeInputText('name') ->sortable(), Column::add() - ->title(__('price')) + ->title('Price') ->field('price') ->sortable() ->makeInputRange('price', '.', ''), Column::add() - ->title(__('Created At')) - ->field('created_at_formatted', 'created_at') + ->title('Created At') + ->field('created_at_formatted') ->makeInputDatePicker('created_at'), ]; } diff --git a/resources/stubs/table.fillable.stub b/resources/stubs/table.fillable.stub index 44907ad6..3ee70da0 100644 --- a/resources/stubs/table.fillable.stub +++ b/resources/stubs/table.fillable.stub @@ -13,10 +13,11 @@ use PowerComponents\LivewirePowerGrid\PowerGridEloquent; use PowerComponents\LivewirePowerGrid\PowerGridComponent; use PowerComponents\LivewirePowerGrid\Traits\ActionButton; -class {{ componentName }} extends PowerGridComponent +final class {{ componentName }} extends PowerGridComponent { use ActionButton; - + + //Messages informing success/error data is updated. public bool $showUpdateMessages = true; /* @@ -26,12 +27,12 @@ class {{ componentName }} extends PowerGridComponent | Setup Table's general features | */ - public function setUp() + public function setUp(): void { $this->showCheckBox() ->showPerPage() - ->showExportOption('download', ['excel', 'csv']) - ->showSearchInput(); + ->showSearchInput() + ->showExportOption('download', ['excel', 'csv']); } /* @@ -53,6 +54,12 @@ class {{ componentName }} extends PowerGridComponent | Configure here relationships to be used by the Search and Table Filters. | */ + + /** + * Relationship search. + * + * @return array> + */ public function relationSearch(): array { return []; @@ -79,6 +86,12 @@ class {{ componentName }} extends PowerGridComponent | Each column can be configured with properties, filters, actions... | */ + + /** + * PowerGrid Columns. + * + * @return array + */ public function columns(): array { return {{ columns }}; @@ -92,17 +105,23 @@ class {{ componentName }} extends PowerGridComponent | */ + /** + * PowerGrid {{ modelLastName }} action buttons. + * + * @return array + */ + /* public function actions(): array { return [ Button::add('edit') - ->caption(__('Edit')) + ->caption('Edit') ->class('bg-indigo-500 text-white') ->route('{{ modelKebabCase }}.edit', ['{{ modelKebabCase }}' => 'id']), Button::add('destroy') - ->caption(__('Delete')) + ->caption('Delete') ->class('bg-red-500 text-white') ->route('{{ modelKebabCase }}.destroy', ['{{ modelKebabCase }}' => 'id']) ->method('delete') @@ -114,38 +133,47 @@ class {{ componentName }} extends PowerGridComponent |-------------------------------------------------------------------------- | Edit Method |-------------------------------------------------------------------------- - | Enable this section to use editOnClick() or toggleable() methods + | Enable this section to use editOnClick() or toggleable() methods. + | Data must be validated and treated (see "Update Data" in PowerGrid doc). | */ + /** + * PowerGrid {{ modelLastName }} Update. + * + * @param array $data + */ + /* public function update(array $data ): bool { try { - $updated = {{ modelLastName }}::query()->find($data['id'])->update([ - $data['field'] => $data['value'] - ]); + $updated = {{ modelLastName }}::query()->findOrFail($data['id']) + ->update([ + $data['field'] => $data['value'], + ]); } catch (QueryException $exception) { $updated = false; } return $updated; } - public function updateMessages(string $status, string $field = '_default_message'): string + public function updateMessages(string $status = 'error', string $field = '_default_message'): string { $updateMessages = [ 'success' => [ '_default_message' => __('Data has been updated successfully!'), - //'custom_field' => __('Custom Field updated successfully!'), + //'custom_field' => __('Custom Field updated successfully!'), ], 'error' => [ '_default_message' => __('Error updating the data.'), - //'custom_field' => __('Error updating custom field.'), + //'custom_field' => __('Error updating custom field.'), ] ]; - return ($updateMessages[$status][$field] ?? $updateMessages[$status]['_default_message']); + $message = ($updateMessages[$status][$field] ?? $updateMessages[$status]['_default_message']); + + return (is_string($message)) ? $message : 'Error!'; } */ - } diff --git a/resources/stubs/table.model.stub b/resources/stubs/table.model.stub index 206299f7..942f08a6 100644 --- a/resources/stubs/table.model.stub +++ b/resources/stubs/table.model.stub @@ -13,10 +13,11 @@ use PowerComponents\LivewirePowerGrid\PowerGridEloquent; use PowerComponents\LivewirePowerGrid\PowerGridComponent; use PowerComponents\LivewirePowerGrid\Traits\ActionButton; -class {{ componentName }} extends PowerGridComponent +final class {{ componentName }} extends PowerGridComponent { use ActionButton; - + + //Messages informing success/error data is updated. public bool $showUpdateMessages = true; /* @@ -26,12 +27,12 @@ class {{ componentName }} extends PowerGridComponent | Setup Table's general features | */ - public function setUp() + public function setUp(): void { $this->showCheckBox() ->showPerPage() - ->showExportOption('download', ['excel', 'csv']) - ->showSearchInput(); + ->showSearchInput() + ->showExportOption('download', ['excel', 'csv']); } /* @@ -41,7 +42,7 @@ class {{ componentName }} extends PowerGridComponent | Provides data to your Table using a Model or Collection | */ - public function datasource(): Builder + public function datasource(): ?Builder { return {{ modelLastName }}::query(); } @@ -53,6 +54,12 @@ class {{ componentName }} extends PowerGridComponent | Configure here relationships to be used by the Search and Table Filters. | */ + + /** + * Relationship search. + * + * @return array> + */ public function relationSearch(): array { return []; @@ -66,7 +73,7 @@ class {{ componentName }} extends PowerGridComponent | You can pass a closure to transform/modify the data. | */ - public function addColumns(): PowerGridEloquent + public function addColumns(): ?PowerGridEloquent { return PowerGrid::eloquent() ->addColumn('id') @@ -85,30 +92,36 @@ class {{ componentName }} extends PowerGridComponent | Each column can be configured with properties, filters, actions... | */ + + /** + * PowerGrid Columns. + * + * @return array + */ public function columns(): array { return [ Column::add() - ->title(__('ID')) + ->title('ID') ->field('id') ->searchable() ->sortable(), Column::add() - ->title(__('Name')) + ->title('Name') ->field('name') ->searchable() ->makeInputText('name') ->sortable(), Column::add() - ->title(__('Created at')) + ->title('Created at') ->field('created_at') ->hidden(), Column::add() - ->title(__('Created at')) - ->field('created_at_formatted', 'created_at') + ->title('Created at') + ->field('created_at_formatted') ->makeInputDatePicker('created_at') ->searchable() ]; @@ -122,17 +135,23 @@ class {{ componentName }} extends PowerGridComponent | */ + /** + * PowerGrid {{ modelLastName }} action buttons. + * + * @return array + */ + /* public function actions(): array { return [ Button::add('edit') - ->caption(__('Edit')) + ->caption('Edit') ->class('bg-indigo-500 text-white') ->route('{{ modelKebabCase }}.edit', ['{{ modelKebabCase }}' => 'id']), Button::add('destroy') - ->caption(__('Delete')) + ->caption('Delete') ->class('bg-red-500 text-white') ->route('{{ modelKebabCase }}.destroy', ['{{ modelKebabCase }}' => 'id']) ->method('delete') @@ -149,20 +168,27 @@ class {{ componentName }} extends PowerGridComponent | */ + /** + * PowerGrid {{ modelLastName }} Update. + * + * @param array $data + */ + /* public function update(array $data ): bool { try { - $updated = {{ modelLastName }}::query()->find($data['id'])->update([ - $data['field'] => $data['value'] - ]); + $updated = {{ modelLastName }}::query() + ->update([ + $data['field'] => $data['value'], + ]); } catch (QueryException $exception) { $updated = false; } return $updated; } - public function updateMessages(string $status, string $field = '_default_message'): string + public function updateMessages(string $status = 'error', string $field = '_default_message'): string { $updateMessages = [ 'success' => [ @@ -175,7 +201,9 @@ class {{ componentName }} extends PowerGridComponent ] ]; - return ($updateMessages[$status][$field] ?? $updateMessages[$status]['_default_message']); + $message = ($updateMessages[$status][$field] ?? $updateMessages[$status]['_default_message']); + + return (is_string($message)) ? $message : 'Error!'; } */ } diff --git a/src/Commands/CreateCommand.php b/src/Commands/CreateCommand.php index e8cc7735..e8c309c0 100644 --- a/src/Commands/CreateCommand.php +++ b/src/Commands/CreateCommand.php @@ -225,7 +225,7 @@ private function createFromFillable(string $modelName, string $modelLastName): s $title = Str::of($field)->replace('_', ' ')->upper(); if (in_array($column->getType()->getName(), ['datetime', 'date'])) { - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '_formatted\', \'' . $field . '\')' . "\n" . ' ->searchable()' . "\n" . ' ->sortable()' . "\n" . ' ->makeInputDatePicker(\'' . $field . '\'),' . "\n\n"; + $columns .= ' Column::add()' . "\n" . ' ->title(\'' . $title . '\')' . "\n" . ' ->field(\'' . $field . '_formatted\', \'' . $field . '\')' . "\n" . ' ->searchable()' . "\n" . ' ->sortable()' . "\n" . ' ->makeInputDatePicker(\'' . $field . '\'),' . "\n\n"; } if ($column->getType()->getName() === 'datetime') { @@ -242,27 +242,27 @@ private function createFromFillable(string $modelName, string $modelLastName): s if ($column->getType()->getName() === 'boolean') { $datasource .= "\n" . ' ->addColumn(\'' . $field . '\')'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->toggleable(),' . "\n\n"; + $columns .= ' Column::add()' . "\n" . ' ->title(\'' . $title . '\')' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->toggleable(),' . "\n\n"; continue; } if (in_array($column->getType()->getName(), ['smallint', 'integer', 'bigint'])) { $datasource .= "\n" . ' ->addColumn(\'' . $field . '\')'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->makeInputRange(),' . "\n\n"; + $columns .= ' Column::add()' . "\n" . ' ->title(\'' . $title . '\')' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->makeInputRange(),' . "\n\n"; continue; } if ($column->getType()->getName() === 'string') { $datasource .= "\n" . ' ->addColumn(\'' . $field . '\')'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->sortable()' . "\n" . ' ->searchable()' . "\n" . ' ->makeInputText(),' . "\n\n"; + $columns .= ' Column::add()' . "\n" . ' ->title(\'' . $title . '\')' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->sortable()' . "\n" . ' ->searchable()' . "\n" . ' ->makeInputText(),' . "\n\n"; continue; } $datasource .= "\n" . ' ->addColumn(\'' . $field . '\')'; - $columns .= ' Column::add()' . "\n" . ' ->title(__(\'' . $title . '\'))' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->sortable()' . "\n" . ' ->searchable(),' . "\n\n"; + $columns .= ' Column::add()' . "\n" . ' ->title(\'' . $title . '\')' . "\n" . ' ->field(\'' . $field . '\')' . "\n" . ' ->sortable()' . "\n" . ' ->searchable(),' . "\n\n"; } $columns .= " ]\n"; From 1c0ace47df23c23a8f234fa643d387952fe946a3 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Thu, 25 Nov 2021 23:29:19 +0100 Subject: [PATCH 25/42] Rename to test:dbs for consistence --- composer.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c62e370a..bd05b210 100644 --- a/composer.json +++ b/composer.json @@ -39,14 +39,14 @@ "doctrine/dbal": "^3.1" }, "scripts": { + "cs-fixer": "./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation", + "fix": "./vendor/bin/php-cs-fixer fix", "test": "@test:sqlite", "test:sqlite": "./vendor/bin/pest --configuration phpunit.sqlite.xml", "test:mysql": "./vendor/bin/pest --configuration phpunit.mysql.xml", "test:pgsql": "./vendor/bin/pest --configuration phpunit.pgsql.xml", "test:types": "./vendor/bin/phpstan analyse --ansi --memory-limit=-1", - "cs-fixer": "./vendor/bin/php-cs-fixer fix --verbose --dry-run --using-cache=no --stop-on-violation", - "fix": "./vendor/bin/php-cs-fixer fix", - "dbs": [ + "test:dbs": [ "@test:sqlite", "@test:mysql", "@test:pgsql" From 6fca742c15c3f9fcb1b2797766f2bd7ba8d25b4d Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Thu, 25 Nov 2021 19:39:56 -0300 Subject: [PATCH 26/42] dev - wip --- .github/workflows/php.yml | 2 +- resources/views/components/row.blade.php | 3 ++- resources/views/components/table.blade.php | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f23a9062..b1099cbd 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -7,7 +7,7 @@ on: pull_request: types: [ ready_for_review, synchronize, opened ] - branches: [ main ] + branches: [ main, dev ] jobs: build: diff --git a/resources/views/components/row.blade.php b/resources/views/components/row.blade.php index af2c9e43..39f9d9e8 100644 --- a/resources/views/components/row.blade.php +++ b/resources/views/components/row.blade.php @@ -2,7 +2,8 @@ 'theme' => null, 'row' => null, 'primaryKey' => null, - 'columns' => null + 'columns' => null, + 'currentTable' => null, ])
@foreach($columns as $column) diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php index bde7daab..3a0c513b 100644 --- a/resources/views/components/table.blade.php +++ b/resources/views/components/table.blade.php @@ -60,6 +60,7 @@ @endif Date: Fri, 26 Nov 2021 01:17:38 -0300 Subject: [PATCH 27/42] fix: Database service ports, adjusted according to the `docker-compose.yml` file and temporary database used as default in SQL Server tests --- phpunit.mysql.xml | 2 +- phpunit.pgsql.xml | 2 +- phpunit.sqlsrv.xml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpunit.mysql.xml b/phpunit.mysql.xml index d08cce57..b31ac686 100644 --- a/phpunit.mysql.xml +++ b/phpunit.mysql.xml @@ -14,7 +14,7 @@ - + diff --git a/phpunit.pgsql.xml b/phpunit.pgsql.xml index 573bd7f3..e9d9d94e 100644 --- a/phpunit.pgsql.xml +++ b/phpunit.pgsql.xml @@ -20,7 +20,7 @@ - + \ No newline at end of file diff --git a/phpunit.sqlsrv.xml b/phpunit.sqlsrv.xml index 978e5d66..37c4306e 100644 --- a/phpunit.sqlsrv.xml +++ b/phpunit.sqlsrv.xml @@ -14,9 +14,9 @@ - - - - + + + + From ac4df72d925fdc9032afea072a5a5977fe2742f2 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Fri, 26 Nov 2021 01:18:40 -0300 Subject: [PATCH 28/42] feature: Enabled Pest tests using SQL Server database. --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index bd05b210..89cda55e 100644 --- a/composer.json +++ b/composer.json @@ -45,11 +45,13 @@ "test:sqlite": "./vendor/bin/pest --configuration phpunit.sqlite.xml", "test:mysql": "./vendor/bin/pest --configuration phpunit.mysql.xml", "test:pgsql": "./vendor/bin/pest --configuration phpunit.pgsql.xml", + "test:sqlsrv": "./vendor/bin/pest --configuration phpunit.sqlsrv.xml", "test:types": "./vendor/bin/phpstan analyse --ansi --memory-limit=-1", "test:dbs": [ "@test:sqlite", "@test:mysql", - "@test:pgsql" + "@test:pgsql", + "@test:sqlsrv" ], "check": [ "@cs-fixer", From ebc3bc6eb90cff1754e65e10d5b0f232195f5670 Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Fri, 26 Nov 2021 01:21:16 -0300 Subject: [PATCH 29/42] feature: implemented the `getDriverVersion` method, responsible for providing the information of the database server version used. --- src/Helpers/SqlSupport.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index f1834eee..2e826ae1 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -2,7 +2,8 @@ namespace PowerComponents\LivewirePowerGrid\Helpers; -use Illuminate\Support\Facades\{DB, Log, Schema}; +use Illuminate\Support\Facades\{DB, Schema}; +use Exception; class SqlSupport { @@ -63,7 +64,7 @@ private static function getSortSqlByDriver(string $sortField, string $driverName } /** - * @param string $sortFieldType + * @param string|null $sortFieldType * @return bool */ public static function isValidSortFieldType(?string $sortFieldType): bool @@ -78,7 +79,7 @@ public static function isValidSortFieldType(?string $sortFieldType): bool /** * @param string $sortField * @return string - * @throws \Exception + * @throws Exception */ public static function getSortFieldType(string $sortField): ?string { @@ -88,7 +89,7 @@ public static function getSortFieldType(string $sortField): ?string } if (!Schema::hasColumn($data[0], $data[1])) { - throw new \Exception("There is no column with name '$data[1]' on table '$data[0]'. Please see: https://livewire-powergrid.docsforge.com/main/include-columns/#fieldstring-field-string-datafield"); + throw new Exception("There is no column with name '$data[1]' on table '$data[0]'. Please see: https://livewire-powergrid.docsforge.com/main/include-columns/#fieldstring-field-string-datafield"); } return Schema::getConnection() @@ -96,4 +97,13 @@ public static function getSortFieldType(string $sortField): ?string ->getType() ->getName(); } + + /** + * @return string + */ + public static function getDriverVersion(): string + { + return DB::getPdo() + ->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); + } } From 4c6840cdbdb8ca5989c59da647f73fa576481c3d Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Fri, 26 Nov 2021 01:53:29 -0300 Subject: [PATCH 30/42] chore: changed name of method `getDriverVersion` to `getServerVersion` --- src/Helpers/SqlSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 2e826ae1..04d0e843 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -101,7 +101,7 @@ public static function getSortFieldType(string $sortField): ?string /** * @return string */ - public static function getDriverVersion(): string + public static function getServerVersion(): string { return DB::getPdo() ->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); From e89a5b13753674bd2ed4bd91e04f678311f85f3e Mon Sep 17 00:00:00 2001 From: vs0uz4 Date: Fri, 26 Nov 2021 08:00:15 -0300 Subject: [PATCH 31/42] style: Applying php-cs-fixer --- src/Helpers/SqlSupport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 04d0e843..6999fd69 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -2,8 +2,8 @@ namespace PowerComponents\LivewirePowerGrid\Helpers; -use Illuminate\Support\Facades\{DB, Schema}; use Exception; +use Illuminate\Support\Facades\{DB, Schema}; class SqlSupport { From 3fafd0e017a6f1c9d952e0723d811210cc824b4f Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 16:02:59 +0100 Subject: [PATCH 32/42] Downgrade postgres --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index b1099cbd..fae67660 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -24,7 +24,7 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: - image: postgres:10.8 + image: postgres:9.6 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres From 5fc27cc59f3559663fa63f7284e82b51481fee85 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 16:06:33 +0100 Subject: [PATCH 33/42] Refactor SqlSupport --- src/Helpers/SqlSupport.php | 72 +++++++++++++----------- tests/Feature/Helpers/SqlSupportTest.php | 32 +++++++++++ 2 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 tests/Feature/Helpers/SqlSupportTest.php diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 6999fd69..484065f0 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -8,16 +8,13 @@ class SqlSupport { /** - * @var array|string[] + * @var array|string[] */ private static array $sortStringNumberTypes = ['string', 'varchar', 'char']; - /** - * @return string - */ public static function like(): string { - $driverName = DB::getDriverName(); + $driverName = self::getDatabaseName(); $likeSyntax = [ 'pgsql' => 'ILIKE', @@ -26,41 +23,49 @@ public static function like(): string return $likeSyntax[$driverName] ?? 'LIKE'; } - /** - * @param string $sortField - * @return string - */ public static function sortStringAsNumber(string $sortField): string { - $driverName = DB::getDriverName(); - - return self::getSortSqlByDriver($sortField, $driverName); + $driverName = self::getDatabaseName(); + $driverVersion = self::getDatabaseVersion(); + + return self::getSortSqlByDriver($sortField, $driverName, $driverVersion); } - /** - * @param string $sortField - * @param string $driverName - * @param string $driverVersion - * @return string - */ - private static function getSortSqlByDriver(string $sortField, string $driverName, string $driverVersion = '*'): string + public static function getSortSqlByDriver(string $sortField, string $driverName = '', string $driverVersion = ''): string { - $sqlByDriver = [ - 'sqlite' => [ - '*' => "CAST($sortField AS INTEGER)", - ], + if (empty($sortField) || empty($driverName) || empty($driverVersion)) { + throw new Exception('sortField, driverName and driverVersion must be informed'); + } + + $default = "$sortField+0"; + + $supportedVersions = [ 'mysql' => [ - '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", + '0' => "$sortField+0", + '8.0.4' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", + ], + 'sqlite' => [ + '0' => "CAST($sortField AS INTEGER)", ], 'pgsql' => [ - '*' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER)", + '0' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '\D', '', 'g'), '') AS INTEGER)", ], 'sqlsrv' => [ - '*' => "CAST(SUBSTRING($sortField, PATINDEX('%[a-z]%', $sortField), LEN($sortField)-PATINDEX('%[a-z]%', $sortField)) AS INT)", + '0' => "CAST(SUBSTRING($sortField, PATINDEX('%[a-z]%', $sortField), LEN($sortField)-PATINDEX('%[a-z]%', $sortField)) AS INT)", ], ]; - return $sqlByDriver[$driverName][$driverVersion] ?? $sortField; + if (!isset($supportedVersions[$driverName])) { + return $default; + } + + $syntax = collect($supportedVersions[$driverName]) + ->filter(function ($syntax, $version) use ($driverVersion) { + return version_compare($version, $driverVersion, '<='); + }) + ->last(); + + return is_null($syntax) === true ? $default : $syntax; } /** @@ -98,12 +103,13 @@ public static function getSortFieldType(string $sortField): ?string ->getName(); } - /** - * @return string - */ - public static function getServerVersion(): string + public static function getDatabaseName(): string + { + return DB::getDriverName(); + } + + public static function getDatabaseVersion(): string { - return DB::getPdo() - ->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); + return DB::getPdo()->getAttribute(constant('PDO::ATTR_SERVER_VERSION')); } } diff --git a/tests/Feature/Helpers/SqlSupportTest.php b/tests/Feature/Helpers/SqlSupportTest.php new file mode 100644 index 00000000..f17d443e --- /dev/null +++ b/tests/Feature/Helpers/SqlSupportTest.php @@ -0,0 +1,32 @@ +not->toBeNull(); +}); + +it('finds database version', function () { + expect(SqlSupport::getDatabaseVersion())->not->toBeNull(); +}); + +it('returns sortField', function (array $data) { + expect(SqlSupport::getSortSqlByDriver('field', $data['db'], $data['version'])) + ->toBe($data['expected']); +})->with([ + [['db' => 'sqlite', 'version' => '3.36.0', 'expected' => 'CAST(field AS INTEGER)']], + [['db' => 'mysql', 'version' => '5.5.59-MariaDB', 'expected' => 'field+0']], + [['db' => 'mysql', 'version' => '5.4.1', 'expected' => 'field+0']], + [['db' => 'mysql', 'version' => '5.7.36', 'expected' => 'field+0']], + [['db' => 'mysql', 'version' => '8.0.3', 'expected' => 'field+0']], + [['db' => 'mysql', 'version' => '8.0.4', 'expected' => "CAST(NULLIF(REGEXP_REPLACE(field, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)"]], + [['db' => 'mysql', 'version' => '8.0.5', 'expected' => "CAST(NULLIF(REGEXP_REPLACE(field, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)"]], + [['db' => 'pgsql', 'version' => '9.6.24', 'expected' => "CAST(NULLIF(REGEXP_REPLACE(field, '\D', '', 'g'), '') AS INTEGER)"]], + [['db' => 'pgsql', 'version' => '13.5', 'expected' => "CAST(NULLIF(REGEXP_REPLACE(field, '\D', '', 'g'), '') AS INTEGER)"]], + [['db' => 'pgsql', 'version' => '15.5', 'expected' => "CAST(NULLIF(REGEXP_REPLACE(field, '\D', '', 'g'), '') AS INTEGER)"]], + [['db' => 'sqlsrv', 'version' => '9.2', 'expected' => "CAST(SUBSTRING(field, PATINDEX('%[a-z]%', field), LEN(field)-PATINDEX('%[a-z]%', field)) AS INT)"]], + [['db' => 'sqlsrv', 'version' => '14.00.3421', 'expected' => "CAST(SUBSTRING(field, PATINDEX('%[a-z]%', field), LEN(field)-PATINDEX('%[a-z]%', field)) AS INT)"]], + [['db' => 'sqlsrv', 'version' => '20.80', 'expected' => "CAST(SUBSTRING(field, PATINDEX('%[a-z]%', field), LEN(field)-PATINDEX('%[a-z]%', field)) AS INT)"]], + [['db' => 'unsupported-db', 'version' => '29.00.00', 'expected' => 'field+0']], + +]); From 2955304443593758ac6f40907d0673b573e5fde6 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 16:20:40 +0100 Subject: [PATCH 34/42] Change mysql to match main version --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index fae67660..30f353e5 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -20,7 +20,7 @@ jobs: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: powergridtest ports: - - 3308:3306 + - 3307:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 postgres: From 98415a0dd5717499fb42e01d76f5af076c49b907 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 16:23:50 +0100 Subject: [PATCH 35/42] Update password according to docker file --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 30f353e5..2a20e674 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -27,7 +27,7 @@ jobs: image: postgres:9.6 env: POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres + POSTGRES_PASSWORD: password POSTGRES_DB: powergridtest ports: - 5433:5432 From 8e3481d82438713fd8d5bbd62f066d6f594551d6 Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 17:11:43 +0100 Subject: [PATCH 36/42] Improve method name and add comments --- src/Helpers/SqlSupport.php | 28 +++++++++++++++++++----- tests/Feature/Helpers/SqlSupportTest.php | 4 ++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index 484065f0..a4320236 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -14,7 +14,15 @@ class SqlSupport public static function like(): string { - $driverName = self::getDatabaseName(); + $driverName = self::getDatabaseDriverName(); + + /* + |-------------------------------------------------------------------------- + | Search 'LIKE' (drivers) + |-------------------------------------------------------------------------- + | PowerGrid needs to sort with "case-insensitive". + | Here, we adapt the 'LIKE' syntax for each database driver. + */ $likeSyntax = [ 'pgsql' => 'ILIKE', @@ -25,7 +33,7 @@ public static function like(): string public static function sortStringAsNumber(string $sortField): string { - $driverName = self::getDatabaseName(); + $driverName = self::getDatabaseDriverName(); $driverVersion = self::getDatabaseVersion(); return self::getSortSqlByDriver($sortField, $driverName, $driverVersion); @@ -37,11 +45,21 @@ public static function getSortSqlByDriver(string $sortField, string $driverName throw new Exception('sortField, driverName and driverVersion must be informed'); } - $default = "$sortField+0"; + /* + |-------------------------------------------------------------------------- + | Supported Databases (drivers) + |-------------------------------------------------------------------------- + | PowerGrid needs to sort string as number. I.e: Rooms 1, 1a, 1b, 2, 3... 10. + | Here, we adapt the SQL sorting syntax between databases and versions. + | 0 => default, + | x.x.x => version which the syntax was implemented. + */ + + $default = "$sortField+0"; //default, fallback $supportedVersions = [ 'mysql' => [ - '0' => "$sortField+0", + '0' => $default, '8.0.4' => "CAST(NULLIF(REGEXP_REPLACE($sortField, '[[:alpha:]]+', ''), '') AS SIGNED INTEGER)", ], 'sqlite' => [ @@ -103,7 +121,7 @@ public static function getSortFieldType(string $sortField): ?string ->getName(); } - public static function getDatabaseName(): string + public static function getDatabaseDriverName(): string { return DB::getDriverName(); } diff --git a/tests/Feature/Helpers/SqlSupportTest.php b/tests/Feature/Helpers/SqlSupportTest.php index f17d443e..612f66e4 100644 --- a/tests/Feature/Helpers/SqlSupportTest.php +++ b/tests/Feature/Helpers/SqlSupportTest.php @@ -2,8 +2,8 @@ use PowerComponents\LivewirePowerGrid\Helpers\SqlSupport; -it('finds database name', function () { - expect(SqlSupport::getDatabaseName())->not->toBeNull(); +it('finds database driver name', function () { + expect(SqlSupport::getDatabaseDriverName())->not->toBeNull(); }); it('finds database version', function () { From 73f7fc8356572fad9a8887970b32804409992cfa Mon Sep 17 00:00:00 2001 From: Daniel Ang Date: Fri, 26 Nov 2021 17:33:24 +0100 Subject: [PATCH 37/42] Adds test for SqlSupport::like --- tests/Feature/Helpers/SqlSupportTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Feature/Helpers/SqlSupportTest.php b/tests/Feature/Helpers/SqlSupportTest.php index 612f66e4..f07a5c45 100644 --- a/tests/Feature/Helpers/SqlSupportTest.php +++ b/tests/Feature/Helpers/SqlSupportTest.php @@ -10,6 +10,29 @@ expect(SqlSupport::getDatabaseVersion())->not->toBeNull(); }); +it('returns the proper "LIKE" syntax', function () { + $driver = SqlSupport::getDatabaseDriverName(); + + expect(SqlSupport::like()) + ->when( + $driver === 'mysql', + fn ($syntax) => $syntax->toBe('LIKE') + ) + ->when( + $driver === 'sqlite', + fn ($syntax) => $syntax->toBe('LIKE') + ) + ->when( + $driver === 'sqlsrv', + fn ($syntax) => $syntax->toBe('LIKE') + ) + ->when( + $driver === 'pgsql', + fn ($syntax) => $syntax->toBe('ILIKE') + ) + ->not->toBeNull(); +}); + it('returns sortField', function (array $data) { expect(SqlSupport::getSortSqlByDriver('field', $data['db'], $data['version'])) ->toBe($data['expected']); From deb8ad93611232537f49ce6e2fcdca8c9d051eed Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Fri, 26 Nov 2021 18:12:24 -0300 Subject: [PATCH 38/42] add preg_replace --- resources/views/components/editable.blade.php | 8 ++-- .../frameworks/bootstrap5/export.blade.php | 20 ++++---- .../frameworks/tailwind/export.blade.php | 22 +++++---- .../frameworks/tailwind/toggleable.blade.php | 48 +++++++++++-------- src/Helpers/SqlSupport.php | 8 +++- src/PowerGridComponent.php | 12 +++-- 6 files changed, 71 insertions(+), 47 deletions(-) diff --git a/resources/views/components/editable.blade.php b/resources/views/components/editable.blade.php index ff0df759..fb760321 100644 --- a/resources/views/components/editable.blade.php +++ b/resources/views/components/editable.blade.php @@ -23,8 +23,12 @@ } else { $content = addslashes($row->{$field}); } + + $content = preg_replace('#(.*?)#is', '', $content); + @endphp
-
+
- + @endif +
diff --git a/src/Helpers/SqlSupport.php b/src/Helpers/SqlSupport.php index a4320236..f3053428 100644 --- a/src/Helpers/SqlSupport.php +++ b/src/Helpers/SqlSupport.php @@ -31,14 +31,20 @@ public static function like(): string return $likeSyntax[$driverName] ?? 'LIKE'; } + /** + * @throws Exception + */ public static function sortStringAsNumber(string $sortField): string { $driverName = self::getDatabaseDriverName(); $driverVersion = self::getDatabaseVersion(); - + return self::getSortSqlByDriver($sortField, $driverName, $driverVersion); } + /** + * @throws Exception + */ public static function getSortSqlByDriver(string $sortField, string $driverName = '', string $driverVersion = ''): string { if (empty($sortField) || empty($driverName) || empty($driverVersion)) { diff --git a/src/PowerGridComponent.php b/src/PowerGridComponent.php index 166b8cc2..a86b9d02 100644 --- a/src/PowerGridComponent.php +++ b/src/PowerGridComponent.php @@ -57,7 +57,7 @@ class PowerGridComponent extends Component public array $relationSearch = []; - public bool $ignoreTablePrefix = false; + public bool $ignoreTablePrefix = true; public bool $showUpdateMessages = false; @@ -367,8 +367,6 @@ public function fillData() $sortField = $this->currentTable . '.' . $this->sortField; } - $sortFieldType = SqlSupport::getSortFieldType($sortField); - /** @var Builder $results */ $results = $this->resolveModel($datasource) ->where(function (Builder $query) { @@ -381,8 +379,12 @@ public function fillData() ->filter(); }); - if ($this->withSortStringNumber && SqlSupport::isValidSortFieldType($sortFieldType)) { - $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); + if ($this->withSortStringNumber) { + $sortFieldType = SqlSupport::getSortFieldType($sortField); + + if (SqlSupport::isValidSortFieldType($sortFieldType)) { + $results->orderByRaw(SqlSupport::sortStringAsNumber($sortField) . ' ' . $this->sortDirection); + } } $results = $results->orderBy($sortField, $this->sortDirection); From 5d4bdbb0496bc638f4c5a9ff263bba4365821928 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Fri, 26 Nov 2021 18:22:03 -0300 Subject: [PATCH 39/42] dev - wip --- tests/Feature/SortTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Feature/SortTest.php b/tests/Feature/SortTest.php index 55b9a41f..e9d6fe50 100644 --- a/tests/Feature/SortTest.php +++ b/tests/Feature/SortTest.php @@ -97,8 +97,9 @@ function () { it('properly sorts ASC/DESC with: string-number') ->livewire(DishesTable::class) - ->set('withSortStringNumber', true) ->set('perPage', '10') + ->set('withSortStringNumber', true) + ->set('ignoreTablePrefix', false) ->call('sortBy', 'stored_at') ->set('sortDirection', 'asc') ->assertSeeHtml('Dish K') From 032e1740da3a6057f0997a3999174c0dba98c919 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Fri, 26 Nov 2021 18:31:50 -0300 Subject: [PATCH 40/42] dev - wip --- resources/views/components/row.blade.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/resources/views/components/row.blade.php b/resources/views/components/row.blade.php index 39f9d9e8..a8286baa 100644 --- a/resources/views/components/row.blade.php +++ b/resources/views/components/row.blade.php @@ -7,6 +7,10 @@ ])
@foreach($columns as $column) + @php + $content = $row->{$column->field}; + $content = preg_replace('#(.*?)#is', '', $content); + @endphp @if($column->hidden === false) @@ -32,11 +36,11 @@ @else
- {!! $row->{$column->field} !!} + {!! $content !!}
From a7a4140356b0ffb6b8dd1a076bb7bb79ba6f3991 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Sat, 27 Nov 2021 06:44:47 -0300 Subject: [PATCH 41/42] fix disabled input --- .../components/filters/input-text.blade.php | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/resources/views/components/filters/input-text.blade.php b/resources/views/components/filters/input-text.blade.php index 86f584ad..717b5811 100644 --- a/resources/views/components/filters/input-text.blade.php +++ b/resources/views/components/filters/input-text.blade.php @@ -53,20 +53,15 @@ class="power_grid {{ $theme->selectClass }} {{ data_get($column, 'headerClass')
- @if(isset($enabledFilters[$field])) - - @else - - @endif + class="power_grid {{ $theme->inputClass }}" + placeholder="{{ empty($column)?data_get($inputText, 'label'):($column->placeholder?:$column->title) }}">
From 6b7c11d002a5f9d7e6e56b7dfb26ec29b8bc4947 Mon Sep 17 00:00:00 2001 From: Luan Freitas Date: Sat, 27 Nov 2021 10:38:58 -0300 Subject: [PATCH 42/42] wip --- resources/stubs/table.fillable.stub | 10 ++--- resources/stubs/table.model.stub | 10 ++--- resources/views/components/actions.blade.php | 22 +++++----- resources/views/components/row.blade.php | 42 ++++++++++---------- resources/views/components/select.blade.php | 2 +- src/Commands/PublishCommand.php | 6 ++- 6 files changed, 48 insertions(+), 44 deletions(-) diff --git a/resources/stubs/table.fillable.stub b/resources/stubs/table.fillable.stub index 3ee70da0..1b77d71f 100644 --- a/resources/stubs/table.fillable.stub +++ b/resources/stubs/table.fillable.stub @@ -16,7 +16,7 @@ use PowerComponents\LivewirePowerGrid\Traits\ActionButton; final class {{ componentName }} extends PowerGridComponent { use ActionButton; - + //Messages informing success/error data is updated. public bool $showUpdateMessages = true; @@ -110,19 +110,19 @@ final class {{ componentName }} extends PowerGridComponent * * @return array */ - + /* public function actions(): array { return [ Button::add('edit') ->caption('Edit') - ->class('bg-indigo-500 text-white') + ->class('bg-indigo-500 cursor-pointer text-white px-3 py-2.5 m-1 rounded text-sm') ->route('{{ modelKebabCase }}.edit', ['{{ modelKebabCase }}' => 'id']), Button::add('destroy') ->caption('Delete') - ->class('bg-red-500 text-white') + ->class('bg-red-500 cursor-pointer text-white px-3 py-2 m-1 rounded text-sm') ->route('{{ modelKebabCase }}.destroy', ['{{ modelKebabCase }}' => 'id']) ->method('delete') ]; @@ -143,7 +143,7 @@ final class {{ componentName }} extends PowerGridComponent * * @param array $data */ - + /* public function update(array $data ): bool { diff --git a/resources/stubs/table.model.stub b/resources/stubs/table.model.stub index 942f08a6..5e17057a 100644 --- a/resources/stubs/table.model.stub +++ b/resources/stubs/table.model.stub @@ -16,7 +16,7 @@ use PowerComponents\LivewirePowerGrid\Traits\ActionButton; final class {{ componentName }} extends PowerGridComponent { use ActionButton; - + //Messages informing success/error data is updated. public bool $showUpdateMessages = true; @@ -140,19 +140,19 @@ final class {{ componentName }} extends PowerGridComponent * * @return array */ - + /* public function actions(): array { return [ Button::add('edit') ->caption('Edit') - ->class('bg-indigo-500 text-white') + ->class('bg-indigo-500 cursor-pointer text-white px-3 py-2.5 m-1 rounded text-sm') ->route('{{ modelKebabCase }}.edit', ['{{ modelKebabCase }}' => 'id']), Button::add('destroy') ->caption('Delete') - ->class('bg-red-500 text-white') + ->class('bg-red-500 cursor-pointer text-white px-3 py-2 m-1 rounded text-sm') ->route('{{ modelKebabCase }}.destroy', ['{{ modelKebabCase }}' => 'id']) ->method('delete') ]; @@ -173,7 +173,7 @@ final class {{ componentName }} extends PowerGridComponent * * @param array $data */ - + /* public function update(array $data ): bool { diff --git a/resources/views/components/actions.blade.php b/resources/views/components/actions.blade.php index 2efc055e..0bbf6232 100644 --- a/resources/views/components/actions.blade.php +++ b/resources/views/components/actions.blade.php @@ -3,11 +3,11 @@ 'theme' => null, 'row' => null ]) -
- @if(isset($actions) && count($actions) && $row !== '') - @foreach($actions as $key => $action) - +@if(isset($actions) && count($actions) && $row !== '') + @foreach($actions as $key => $action) + +
@php foreach ($action->param as $param => $value) { if (!empty($row->{$value})) { @@ -36,19 +36,19 @@ class="{{ filled($action->class) ? $action->class : $theme->actions->headerBtnCl @method($action->method) @csrf @else {!! $action->caption !!} @endif @endif - - @endforeach - @endif -
+
+ + @endforeach +@endif diff --git a/resources/views/components/row.blade.php b/resources/views/components/row.blade.php index a8286baa..e1fa68c5 100644 --- a/resources/views/components/row.blade.php +++ b/resources/views/components/row.blade.php @@ -5,18 +5,18 @@ 'columns' => null, 'currentTable' => null, ]) -
- @foreach($columns as $column) - @php - $content = $row->{$column->field}; - $content = preg_replace('#(.*?)#is', '', $content); - @endphp - @if($column->hidden === false) - - @if($column->editable === true) - + +@foreach($columns as $column) + @php + $content = $row->{$column->field}; + $content = preg_replace('#(.*?)#is', '', $content); + @endphp + @if($column->hidden === false) + + @if($column->editable === true) + - @elseif(count($column->toggleable) > 0) - @include($theme->toggleable->view) - @else - + @elseif(count($column->toggleable) > 0) + @include($theme->toggleable->view) + @else +
{!! $content !!}
@@ -44,8 +44,8 @@ :label="data_get($column->clickToCopy, 'label') ?? null" :enabled="data_get($column->clickToCopy, 'enabled') ?? false"/>
- @endif - - @endif - @endforeach -
+ @endif + + @endif +@endforeach + diff --git a/resources/views/components/select.blade.php b/resources/views/components/select.blade.php index e002e858..f5089b55 100644 --- a/resources/views/components/select.blade.php +++ b/resources/views/components/select.blade.php @@ -5,7 +5,7 @@ 'theme' => null ])
- @foreach($data as $value => $label) diff --git a/src/Commands/PublishCommand.php b/src/Commands/PublishCommand.php index 9a8ca86c..b8bfb251 100644 --- a/src/Commands/PublishCommand.php +++ b/src/Commands/PublishCommand.php @@ -2,6 +2,7 @@ namespace PowerComponents\LivewirePowerGrid\Commands; +use Exception; use Illuminate\Console\Command; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Facades\File; @@ -12,13 +13,16 @@ class PublishCommand extends Command protected $description = 'Publish table stub'; + /** + * @throws Exception + */ public function handle(): void { if ($this->option('type') === 'job') { $exportJobFile = __DIR__ . '/../Jobs/ExportJob.php'; if (File::exists($exportJobFile) === false && File::isReadable($exportJobFile) === true) { - throw new \Exception('ExportJob.php not found.'); + throw new Exception('ExportJob.php not found.'); } $file = (string) file_get_contents($exportJobFile);