diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml index 463a629a..ce1e09d2 100644 --- a/.config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -79,7 +79,13 @@ + + + + + + diff --git a/.config/pmd/java/ruleset.xml b/.config/pmd/java/ruleset.xml index e96576b1..e42e77a4 100644 --- a/.config/pmd/java/ruleset.xml +++ b/.config/pmd/java/ruleset.xml @@ -146,7 +146,6 @@ - @@ -164,6 +163,7 @@ + @@ -208,6 +208,36 @@ + + +`Optional#get` can be interpreted as a getter by developers, however this is not the case as it throws an exception when empty. + +It should be replaced by +* doing a mapping directly using `.map` or `.ifPresent` +* using the preferred `.orElseThrow`, `.orElse` or `.or` methods + +Java Developer Brian Goetz also writes regarding this topic: + +> Java 8 was a huge improvement to the platform, but one of the few mistakes we made was the naming of `Optional.get()`, because the name just invites people to call it without calling `isPresent()`, undermining the whole point of using `Optional` in the first place. +> +> During the Java 9 time frame, we proposed to deprecate `Optional.get()`, but the public response to that was ... let's say cold. As a smaller step, we introduced `orElseThrow()` in 10 (see [JDK-8140281](https://bugs.openjdk.java.net/browse/JDK-8140281)) as a more transparently named synonym for the current pernicious behavior of `get()`. IDEs warn on unconditional use of `get()`, but not on `orElseThrow()`, which is a step forward in teaching people to code better. The question is, in a sense, a "glass half empty" view of the current situation; `get()` is still problematic. + + 3 + + + + + + + + + diff --git a/.config/topo/upstream.yml b/.config/topo/upstream.yml new file mode 100644 index 00000000..fced0c57 --- /dev/null +++ b/.config/topo/upstream.yml @@ -0,0 +1,2 @@ +- url: https://github.com/xdev-software/vaadin-addon-template.git + branch: master diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 2675c8b6..7b1481c5 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -3,7 +3,7 @@ name: Broken links on: workflow_dispatch: schedule: - - cron: "23 23 * * 0" + - cron: "23 5 * * 0" permissions: issues: write @@ -19,8 +19,9 @@ jobs: - name: Link Checker id: lychee - uses: lycheeverse/lychee-action@a8c4c7cb88f0c7386610c35eb25108e448569cb0 # v2 + uses: lycheeverse/lychee-action@8646ba30535128ac92d33dfc9133794bfdd9b411 # v2 with: + args: "--verbose --no-progress './**/*.md'" fail: false # Don't fail on broken links, create an issue instead - name: Find already existing issue diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 5acbddfd..0a788ee4 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -78,7 +78,7 @@ jobs: fi - name: Upload demo files - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@v7 with: name: demo-files-java-${{ matrix.java }} path: ${{ env.DEMO_MAVEN_MODULE }}/target/${{ env.DEMO_MAVEN_MODULE }}.jar @@ -160,8 +160,8 @@ jobs: run: ./mvnw -B pmd:aggregate-cpd pmd:cpd-check -P pmd -DskipTests -T2C - name: Upload report - if: always() - uses: actions/upload-artifact@v6 + if: ${{ !cancelled() }} + uses: actions/upload-artifact@v7 with: name: pmd-report if-no-files-found: ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6a53a5bf..4aebeeb5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,7 +36,7 @@ jobs: ${{ runner.os }}-mvn-build- - name: Build with Maven - run: ./mvnw -B clean package -Pproduction -T2C + run: ./mvnw -B clean package -T2C - name: Check for uncommited changes run: | @@ -91,7 +91,7 @@ jobs: - name: Create Release id: create-release - uses: shogo82148/actions-create-release@559c27ce7eb834825e2b55927c64f6d1bd1db716 # v1 + uses: shogo82148/actions-create-release@6a396031bc74c57403da1018fec74d24c6aa03cd # v1 with: tag_name: v${{ steps.version.outputs.release }} release_name: v${{ steps.version.outputs.release }} diff --git a/.github/workflows/report-gha-workflow-security-problems.yml b/.github/workflows/report-gha-workflow-security-problems.yml new file mode 100644 index 00000000..b17aa539 --- /dev/null +++ b/.github/workflows/report-gha-workflow-security-problems.yml @@ -0,0 +1,61 @@ +name: Report workflow security problems + +on: + workflow_dispatch: + push: + branches: [ develop ] + paths: + - '.github/workflows/**' + +permissions: + issues: write + +jobs: + prt: + runs-on: ubuntu-latest + timeout-minutes: 15 + # Only run this in our repos (Prevent notification spam by forks) + if: ${{ github.repository_owner == 'xdev-software' }} + steps: + - uses: actions/checkout@v6 + + - name: Check + id: check + run: | + grep -l 'pull_request_target:' --exclude report-gha-workflow-security-problems.yml *.yml > reported.txt && exit 1 || exit 0 + working-directory: .github/workflows + + - name: Find already existing issue + id: find-issue + if: ${{ !cancelled() }} + run: | + echo "number=$(gh issue list -l 'bug' -l 'automated' -L 1 -S 'in:title "Incorrectly configure GHA workflow (prt)"' -s 'open' --json 'number' --jq '.[].number')" >> $GITHUB_OUTPUT + env: + GH_TOKEN: ${{ github.token }} + + - name: Close issue if everything is fine + if: ${{ success() && steps.find-issue.outputs.number != '' }} + run: gh issue close -r 'not planned' ${{ steps.find-issue.outputs.number }} + env: + GH_TOKEN: ${{ github.token }} + + - name: Create report + if: ${{ failure() && steps.check.conclusion == 'failure' }} + run: | + echo 'Detected usage of `pull_request_target`. This event is dangerous and MUST NOT BE USED AT ALL COST!' > reported.md + echo '' >> reported.md + echo '/cc @xdev-software/gha-workflow-security' >> reported.md + echo '' >> reported.md + echo '```' >> reported.md + cat .github/workflows/reported.txt >> reported.md + echo '```' >> reported.md + cat reported.md + + - name: Create Issue From File + if: ${{ failure() && steps.check.conclusion == 'failure' }} + uses: peter-evans/create-issue-from-file@fca9117c27cdc29c6c4db3b86c48e4115a786710 # v6 + with: + issue-number: ${{ steps.find-issue.outputs.number }} + title: 'Incorrectly configure GHA workflow (prt)' + content-filepath: ./reported.md + labels: bug, automated diff --git a/.github/workflows/update-from-template.yml b/.github/workflows/update-from-template.yml deleted file mode 100644 index a5e3b956..00000000 --- a/.github/workflows/update-from-template.yml +++ /dev/null @@ -1,320 +0,0 @@ -name: Update from Template - -# This workflow keeps the repo up to date with changes from the template repo (REMOTE_URL) -# It duplicates the REMOTE_BRANCH (into UPDATE_BRANCH) and tries to merge it into -# this repos default branch (which is checked out here) -# Note that this requires a PAT (Personal Access Token) - at best from a servicing account -# PAT permissions: read:discussion, read:org, repo, workflow -# Also note that you should have at least once merged the template repo into the current repo manually -# otherwise a "refusing to merge unrelated histories" error might occur. - -on: - schedule: - - cron: '55 2 * * 1' - workflow_dispatch: - inputs: - no_automatic_merge: - type: boolean - description: 'No automatic merge' - default: false - -env: - UPDATE_BRANCH: update-from-template - UPDATE_BRANCH_MERGED: update-from-template-merged - REMOTE_URL: https://github.com/xdev-software/vaadin-addon-template.git - REMOTE_BRANCH: master - -permissions: - contents: write - pull-requests: write - -jobs: - update: - runs-on: ubuntu-latest - timeout-minutes: 60 - outputs: - update_branch_merged_commit: ${{ steps.manage-branches.outputs.update_branch_merged_commit }} - create_update_branch_merged_pr: ${{ steps.manage-branches.outputs.create_update_branch_merged_pr }} - steps: - - uses: actions/checkout@v6 - with: - # Required because otherwise there are always changes detected when executing diff/rev-list - fetch-depth: 0 - # If no PAT is used the following error occurs on a push: - # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission - token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - - - name: Init Git - run: | - git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" - git config --global user.name "XDEV Bot" - - - name: Manage branches - id: manage-branches - run: | - echo "Adding remote template-repo" - git remote add template ${{ env.REMOTE_URL }} - - echo "Fetching remote template repo" - git fetch template - - echo "Deleting local branches that will contain the updates - if present" - git branch -D ${{ env.UPDATE_BRANCH }} || true - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "Checking if the remote template repo has new commits" - git rev-list ..template/${{ env.REMOTE_BRANCH }} - - if [ $(git rev-list --count ..template/${{ env.REMOTE_BRANCH }}) -eq 0 ]; then - echo "There are no commits new commits on the template repo" - - echo "Deleting origin branch(es) that contain the updates - if present" - git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Found new commits on the template repo" - - echo "Creating update branch" - git branch ${{ env.UPDATE_BRANCH }} template/${{ env.REMOTE_BRANCH }} - git branch --unset-upstream ${{ env.UPDATE_BRANCH }} - - echo "Pushing update branch" - git push -f -u origin ${{ env.UPDATE_BRANCH }} - - echo "Getting base branch" - base_branch=$(git branch --show-current) - echo "Base branch is $base_branch" - echo "base_branch=$base_branch" >> $GITHUB_OUTPUT - - echo "Trying to create auto-merged branch ${{ env.UPDATE_BRANCH_MERGED }}" - git branch ${{ env.UPDATE_BRANCH_MERGED }} ${{ env.UPDATE_BRANCH }} - git checkout ${{ env.UPDATE_BRANCH_MERGED }} - - echo "Merging branch $base_branch into ${{ env.UPDATE_BRANCH_MERGED }}" - git merge $base_branch && merge_exit_code=$? || merge_exit_code=$? - if [ $merge_exit_code -ne 0 ]; then - echo "Auto merge failed! Manual merge required" - echo "::notice ::Auto merge failed - Manual merge required" - - echo "Cleaning up failed merge" - git merge --abort - git checkout $base_branch - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "Deleting auto-merge branch - if present" - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true - - echo "create_update_branch_pr=1" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Post processing: Trying to automatically fill in template variables" - find . -type f \ - -not -path "./.git/**" \ - -not -path "./.github/workflows/update-from-template.yml" -print0 \ - | xargs -0 sed -i "s/template-placeholder/${GITHUB_REPOSITORY#*/}/g" - - git status - git add --all - - if [[ "$(git status --porcelain)" != "" ]]; then - echo "Filled in template; Committing" - - git commit -m "Fill in template" - fi - - echo "Pushing auto-merged branch" - git push -f -u origin ${{ env.UPDATE_BRANCH_MERGED }} - - echo "update_branch_merged_commit=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - echo "Restoring base branch $base_branch" - git checkout $base_branch - - echo "create_update_branch_pr=0" >> $GITHUB_OUTPUT - echo "create_update_branch_merged_pr=1" >> $GITHUB_OUTPUT - echo "try_close_update_branch_pr=1" >> $GITHUB_OUTPUT - - - name: Create/Update PR update_branch - if: steps.manage-branches.outputs.create_update_branch_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh_pr_up() { - gh pr create -H "${{ env.UPDATE_BRANCH }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH }}" && gh pr edit "$@") - } - gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ - --title "Update from template" \ - --body "An automated PR to sync changes from the template into this repo" - - # Ensure that only a single PR is open (otherwise confusion and spam) - - name: Close PR update_branch - if: steps.manage-branches.outputs.try_close_update_branch_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh pr close "${{ env.UPDATE_BRANCH }}" || true - - - name: Create/Update PR update_branch_merged - if: steps.manage-branches.outputs.create_update_branch_merged_pr == 1 - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - gh_pr_up() { - gh pr create -H "${{ env.UPDATE_BRANCH_MERGED }}" "$@" || (git checkout "${{ env.UPDATE_BRANCH_MERGED }}" && gh pr edit "$@") - } - gh_pr_up -B "${{ steps.manage-branches.outputs.base_branch }}" \ - --title "Update from template (auto-merged)" \ - --body "An automated PR to sync changes from the template into this repo" - - # Wait a moment so that checks of PR have higher prio than following job - sleep 3 - - # Split into two jobs to help with executor starvation - auto-merge: - needs: [update] - if: needs.update.outputs.create_update_branch_merged_pr == 1 - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - uses: actions/checkout@v6 - with: - # Required because otherwise there are always changes detected when executing diff/rev-list - fetch-depth: 0 - # If no PAT is used the following error occurs on a push: - # refusing to allow a GitHub App to create or update workflow `.github/workflows/xxx.yml` without `workflows` permission - token: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - - - name: Init Git - run: | - git config --global user.email "111048771+xdev-gh-bot@users.noreply.github.com" - git config --global user.name "XDEV Bot" - - - name: Checking if auto-merge for PR update_branch_merged can be done - id: auto-merge-check - env: - GH_TOKEN: ${{ secrets.UPDATE_FROM_TEMPLATE_PAT }} - run: | - not_failed_conclusion="skipped|neutral|success" - not_relevant_app_slug="dependabot|github-pages|sonarqubecloud" - - echo "Waiting for checks to start..." - sleep 40s - - for i in {1..20}; do - echo "Checking if PR can be auto-merged. Try: $i" - - echo "Checking if update-branch-merged exists" - git fetch - if [[ $(git ls-remote --heads origin refs/heads/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then - echo "Branch still exists; Continuing..." - else - echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" - exit 0 - fi - - echo "Fetching checks" - cs_response=$(curl -sL \ - --fail-with-body \ - --connect-timeout 60 \ - --max-time 120 \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GH_TOKEN" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - https://api.github.com/repos/${{ github.repository }}/commits/${{ needs.update.outputs.update_branch_merged_commit }}/check-suites) - - cs_data=$(echo $cs_response | jq '.check_suites[] | { conclusion: .conclusion, slug: .app.slug, check_runs_url: .check_runs_url }') - echo $cs_data - - if [[ -z "$cs_data" ]]; then - echo "No check suite data - Assuming that there are no checks to run" - - echo "perform=1" >> $GITHUB_OUTPUT - exit 0 - fi - - cs_failed=$(echo $cs_data | jq --arg x "$not_failed_conclusion" 'select ((.conclusion == null or (.conclusion | test($x))) | not)') - if [[ -z "$cs_failed" ]]; then - echo "No check failed so far; Checking if relevant checks are still running" - - cs_relevant_still_running=$(echo $cs_data | jq --arg x "$not_relevant_app_slug" 'select (.conclusion == null and (.slug | test($x) | not))') - if [[ -z $cs_relevant_still_running ]]; then - echo "All relevant checks finished - PR can be merged" - - echo "perform=1" >> $GITHUB_OUTPUT - exit 0 - else - echo "Relevant checks are still running" - echo $cs_relevant_still_running - fi - else - echo "Detected failed check" - echo $cs_failed - - echo "perform=0" >> $GITHUB_OUTPUT - exit 0 - fi - - echo "Waiting before next run..." - sleep 30s - done - - echo "Timed out - Assuming executor starvation - Forcing merge" - echo "perform=1" >> $GITHUB_OUTPUT - - - name: Auto-merge update_branch_merged - if: steps.auto-merge-check.outputs.perform == 1 - run: | - echo "Getting base branch" - base_branch=$(git branch --show-current) - echo "Base branch is $base_branch" - - echo "Fetching..." - git fetch - if [[ $(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) ]]; then - echo "Branch still exists; Continuing..." - else - echo "Branch origin/${{ env.UPDATE_BRANCH_MERGED }} is missing" - exit 0 - fi - - expected_commit="${{ needs.update.outputs.update_branch_merged_commit }}" - actual_commit=$(git rev-parse origin/${{ env.UPDATE_BRANCH_MERGED }}) - if [[ "$expected_commit" != "$actual_commit" ]]; then - echo "Branch ${{ env.UPDATE_BRANCH_MERGED }} contains unexpected commit $actual_commit" - echo "Expected: $expected_commit" - - exit 0 - fi - - echo "Ensuring that current branch $base_branch is up-to-date" - git pull - - echo "Merging origin/${{ env.UPDATE_BRANCH_MERGED }} into $base_branch" - git merge origin/${{ env.UPDATE_BRANCH_MERGED }} && merge_exit_code=$? || merge_exit_code=$? - if [ $merge_exit_code -ne 0 ]; then - echo "Unexpected merge failure $merge_exit_code - Requires manual resolution" - - exit 0 - fi - - if [[ "${{ inputs.no_automatic_merge }}" == "true" ]]; then - echo "Exiting due no_automatic_merge" - - exit 0 - fi - - echo "Pushing" - git push - - echo "Cleaning up" - git branch -D ${{ env.UPDATE_BRANCH }} || true - git branch -D ${{ env.UPDATE_BRANCH_MERGED }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH }} || true - git push -f origin --delete ${{ env.UPDATE_BRANCH_MERGED }} || true diff --git a/.gitignore b/.gitignore index 53935857..85928e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,8 @@ node_modules/ # Vaadin package.json package-lock.json +.npmrc +pnpm-lock.yaml webpack.generated.js webpack.config.js tsconfig.json diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index a751c417..27f72cee 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 13.0.0 + 13.4.0 JavaOnlyWithTests true true diff --git a/.idea/externalDependencies.xml b/.idea/externalDependencies.xml index 78be5b8e..0b477b88 100644 --- a/.idea/externalDependencies.xml +++ b/.idea/externalDependencies.xml @@ -3,5 +3,6 @@ + \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8dea6c22..216df058 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index e1408d2e..64190053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 3.1.0 +* Added `is not empty` +* Added a demo to show how custom type handling can be done + # 3.0.0 * Updated to Vaadin 25 diff --git a/pom.xml b/pom.xml index 1e31d181..163c8eb5 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.xdev vaadin-grid-filter-root - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT pom @@ -45,7 +45,7 @@ com.puppycrawl.tools checkstyle - 13.0.0 + 13.4.2 @@ -83,12 +83,12 @@ net.sourceforge.pmd pmd-core - 7.20.0 + 7.24.0 net.sourceforge.pmd pmd-java - 7.20.0 + 7.24.0 diff --git a/vaadin-grid-filter-demo/pnpm-workspace.yaml b/vaadin-grid-filter-demo/pnpm-workspace.yaml index 9ac0828b..3ac055f8 100644 --- a/vaadin-grid-filter-demo/pnpm-workspace.yaml +++ b/vaadin-grid-filter-demo/pnpm-workspace.yaml @@ -1,25 +1,13 @@ # Delay install of newly released packages to prevent supply chain attacks minimumReleaseAge: 180 # 3h +minimumReleaseAgeExclude: + - "@xdevsoftware/*" overrides: # Remove unused packages - # glob CLI unused - "jackspeak": "npm:empty-npm-package@1.0.0" - "foreground-child": "npm:empty-npm-package@1.0.0" - "package-json-from-dist": "npm:empty-npm-package@1.0.0" # rollup-plugin-visualizer CLI unused "yargs": "npm:empty-npm-package@1.0.0" "open": "npm:empty-npm-package@1.0.0" # transform-ast test only "nanobench": "npm:empty-npm-package@1.0.0" - # Workbox unused - "workbox-google-analytics": "npm:empty-npm-package@1.0.0" - "@surma/rollup-plugin-off-main-thread": "npm:empty-npm-package@1.0.0" - "@babel/preset-env": "npm:empty-npm-package@1.0.0" - "@babel/runtime": "npm:empty-npm-package@1.0.0" - "@rollup/plugin-replace@2.4.2": "npm:empty-npm-package@1.0.0" - "@rollup/plugin-babel": "npm:empty-npm-package@1.0.0" - "@rollup/plugin-node-resolve": "npm:empty-npm-package@1.0.0" - "@rollup/plugin-terser": "npm:empty-npm-package@1.0.0" - "tempy": "npm:empty-npm-package@1.0.0" - # Disable telemetry - "@vaadin/vaadin-usage-statistics": "npm:empty-npm-package@1.0.0" + # Usage statistics + "@vaadin/vaadin-usage-statistics": "npm:@xdevsoftware/vaadin-usage-statistics-opt-out@1.0.2" diff --git a/vaadin-grid-filter-demo/pom.xml b/vaadin-grid-filter-demo/pom.xml index 57e8ba3e..eb9993fa 100644 --- a/vaadin-grid-filter-demo/pom.xml +++ b/vaadin-grid-filter-demo/pom.xml @@ -7,11 +7,11 @@ software.xdev vaadin-grid-filter-root - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT vaadin-grid-filter-demo - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT jar @@ -29,9 +29,9 @@ software.xdev.vaadin.Application - 25.0.3 + 25.1.5 - 4.0.1 + 4.0.6 @@ -62,11 +62,15 @@ com.vaadin vaadin-core - + com.vaadin collaboration-engine + + com.vaadin + vaadin-ai-components-flow + @@ -152,7 +156,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 ${maven.compiler.release} diff --git a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/DemoView.java b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/DemoView.java index 705792d2..41cea3d1 100644 --- a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/DemoView.java +++ b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/DemoView.java @@ -13,6 +13,7 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; +import software.xdev.vaadin.gridfilter.demo.CustomTypeDemo; import software.xdev.vaadin.gridfilter.demo.LocalizationDemo; import software.xdev.vaadin.gridfilter.demo.MaxNestedDepthDemo; import software.xdev.vaadin.gridfilter.demo.MinimalisticDemo; @@ -71,6 +72,11 @@ protected void onAttach(final AttachEvent attachEvent) LocalizationDemo.NAV, "Localization", "Showcases how localization can be done (UI in German)" + ), + new Example( + CustomTypeDemo.NAV, + "Custom type", + "Shows how a custom type can be handled" ) )); } diff --git a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/demo/CustomTypeDemo.java b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/demo/CustomTypeDemo.java new file mode 100644 index 00000000..dfedbef0 --- /dev/null +++ b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/demo/CustomTypeDemo.java @@ -0,0 +1,42 @@ +package software.xdev.vaadin.gridfilter.demo; + +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.details.Details; +import com.vaadin.flow.component.details.DetailsVariant; +import com.vaadin.flow.router.Route; + +import software.xdev.vaadin.gridfilter.GridFilter; +import software.xdev.vaadin.gridfilter.business.typevaluecomp.single.SingleValueComponentProvider; +import software.xdev.vaadin.gridfilter.model.City; +import software.xdev.vaadin.gridfilter.model.Person; + + +@Route(CustomTypeDemo.NAV) +public class CustomTypeDemo extends AbstractDemo +{ + public static final String NAV = "/customtype"; + + public CustomTypeDemo() + { + final GridFilter filter = this.createDefaultFilter() + .addTypeValueComponent(new SingleValueComponentProvider<>( + City.class, + () -> { + final ComboBox comboBox = new ComboBox<>(); + comboBox.setItemLabelGenerator(City::name); + comboBox.setItems(City.allAvailable().values()); + return comboBox; + }, + City::name, + name -> City.allAvailable().get(name) + )) + .withFilterableField("City", Person::city, City.class); + + // Add filter inside details block for better looking UI + final Details details = new Details("Filter data"); + details.addThemeVariants(DetailsVariant.LUMO_FILLED); + details.add(filter); + details.setOpened(true); + this.add(details, this.grid); + } +} diff --git a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/City.java b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/City.java new file mode 100644 index 00000000..848b6b60 --- /dev/null +++ b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/City.java @@ -0,0 +1,27 @@ +package software.xdev.vaadin.gridfilter.model; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +public record City( + String name +) +{ + public static final City AMSTERDAM = new City("Amsterdam"); + public static final City BERLIN = new City("Berlin"); + public static final City NEW_YORK = new City("New York"); + + public static Map allAvailable() + { + return Stream.of(AMSTERDAM, BERLIN, NEW_YORK) + .collect(Collectors.toMap( + City::name, + Function.identity(), + (l, r) -> l, + LinkedHashMap::new)); + } +} diff --git a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/Person.java b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/Person.java index f2a30edd..447ae5b5 100644 --- a/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/Person.java +++ b/vaadin-grid-filter-demo/src/main/java/software/xdev/vaadin/gridfilter/model/Person.java @@ -3,6 +3,8 @@ import java.time.LocalDate; import java.util.List; +import org.jspecify.annotations.Nullable; + public record Person( Integer id, @@ -11,22 +13,32 @@ public record Person( LocalDate birthday, boolean married, double salary, - Department department + Department department, + @Nullable + City city ) { @SuppressWarnings("checkstyle:MagicNumber") public static List list() { return List.of( - new Person(0, "Siegbert", "Schmidt", LocalDate.of(1990, 12, 17), false, 1000, Department.HR), - new Person(1, "Herbert", "Maier", LocalDate.of(1967, 10, 13), false, 1000, Department.HR), - new Person(2, "Hans", "Lang", LocalDate.of(2002, 5, 9), true, 9050.60, Department.HR), - new Person(3, "Anton", "Meier", LocalDate.of(1985, 1, 24), true, 8000.75, Department.HR), - new Person(4, "Sarah", "Smith", LocalDate.of(1999, 6, 1), false, 5000, Department.IT), - new Person(5, "Niklas", "Sommer", LocalDate.of(1994, 11, 8), true, 4000.33, Department.HR), - new Person(6, "Hanna", "Neubaum", LocalDate.of(1986, 8, 15), true, 3000, Department.HR), - new Person(8, "Laura", "Fels", LocalDate.of(1996, 3, 20), true, 1000.50, Department.HR), - new Person(7, "Sofia", "Sommer", LocalDate.of(1972, 4, 14), false, 2000, Department.ACCOUNTING) + new Person( + 1, + "Siegbert", + "Schmidt", + LocalDate.of(1990, 12, 17), + false, + 1000, + Department.HR, + City.AMSTERDAM), + new Person(2, "Herbert", "Maier", LocalDate.of(1967, 10, 13), false, 1000, Department.HR, City.AMSTERDAM), + new Person(3, "Hans", "Lang", LocalDate.of(2002, 5, 9), true, 9050.60, Department.HR, City.AMSTERDAM), + new Person(4, "Anton", "Meier", LocalDate.of(1985, 1, 24), true, 8000.75, Department.HR, City.NEW_YORK), + new Person(5, "Sarah", "Smith", LocalDate.of(1999, 6, 1), false, 5000, Department.IT, City.BERLIN), + new Person(6, "Niklas", "Sommer", LocalDate.of(1994, 11, 8), true, 4000.33, Department.HR, City.BERLIN), + new Person(7, "Hanna", "Neubaum", LocalDate.of(1986, 8, 15), true, 3000, Department.HR, null), + new Person(8, "Laura", "Fels", LocalDate.of(1996, 3, 20), true, 1000.50, Department.HR, null), + new Person(9, "Sofia", "Sommer", LocalDate.of(1972, 4, 14), false, 2000, Department.ACCOUNTING, null) ); } } diff --git a/vaadin-grid-filter/pom.xml b/vaadin-grid-filter/pom.xml index 275341fc..24f806f7 100644 --- a/vaadin-grid-filter/pom.xml +++ b/vaadin-grid-filter/pom.xml @@ -6,7 +6,7 @@ software.xdev vaadin-grid-filter - 3.0.1-SNAPSHOT + 3.1.0-SNAPSHOT jar Grid Filter for Vaadin @@ -49,7 +49,7 @@ UTF-8 - 25.0.3 + 25.1.5 @@ -119,7 +119,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.14.1 + 3.15.0 ${maven.compiler.release} @@ -261,7 +261,7 @@ com.puppycrawl.tools checkstyle - 13.0.0 + 13.4.2 @@ -299,12 +299,12 @@ net.sourceforge.pmd pmd-core - 7.20.0 + 7.24.0 net.sourceforge.pmd pmd-java - 7.20.0 + 7.24.0 diff --git a/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilter.java b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilter.java index 4f87f3fc..813da000 100644 --- a/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilter.java +++ b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilter.java @@ -54,6 +54,7 @@ import software.xdev.vaadin.gridfilter.business.operation.EqualsOp; import software.xdev.vaadin.gridfilter.business.operation.GreaterThanOp; import software.xdev.vaadin.gridfilter.business.operation.IsEmptyOp; +import software.xdev.vaadin.gridfilter.business.operation.IsNotEmptyOp; import software.xdev.vaadin.gridfilter.business.operation.LessThanOp; import software.xdev.vaadin.gridfilter.business.operation.Operation; import software.xdev.vaadin.gridfilter.business.typevaluecomp.TypeValueComponentProvider; @@ -406,7 +407,8 @@ public static GridFilter createDefault(final Grid grid) new GreaterThanOp(), new LessThanOp(), new ContainsOp(), - new IsEmptyOp() + new IsEmptyOp(), + new IsNotEmptyOp() )) .addTypeValueComponents(List.of( new NoValueComponentProvider(), diff --git a/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilterLocalizationConfig.java b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilterLocalizationConfig.java index 5ae55f23..819bac49 100644 --- a/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilterLocalizationConfig.java +++ b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/GridFilterLocalizationConfig.java @@ -34,6 +34,7 @@ public class GridFilterLocalizationConfig public static final String OP_GREATER_THAN = PREFIX_OP + "gt"; public static final String OP_LESS_THAN = PREFIX_OP + "lt"; public static final String OP_IS_EMPTY = PREFIX_OP + "empty"; + public static final String OP_IS_NOT_EMPTY = PREFIX_OP + "not_empty"; public static final String PREFIX_BLOCK = PREFIX + "block."; public static final String BLOCK_OR = PREFIX_BLOCK + "or"; @@ -49,6 +50,7 @@ public class GridFilterLocalizationConfig entry(OP_GREATER_THAN, ">"), entry(OP_LESS_THAN, "<"), entry(OP_IS_EMPTY, "is empty"), + entry(OP_IS_NOT_EMPTY, "is not empty"), // Block entry(BLOCK_OR, "OR"), entry(BLOCK_AND, "AND"), diff --git a/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/business/operation/IsNotEmptyOp.java b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/business/operation/IsNotEmptyOp.java new file mode 100644 index 00000000..004fd462 --- /dev/null +++ b/vaadin-grid-filter/src/main/java/software/xdev/vaadin/gridfilter/business/operation/IsNotEmptyOp.java @@ -0,0 +1,58 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.vaadin.gridfilter.business.operation; + +import software.xdev.vaadin.gridfilter.GridFilterLocalizationConfig; +import software.xdev.vaadin.gridfilter.business.value.NoValue; + + +public class IsNotEmptyOp implements Operation +{ + @Override + public Class valueContainerClass() + { + return NoValue.class; + } + + @Override + public boolean canHandle(final Class clazz) + { + return true; + } + + @Override + public String identifier() + { + return "is not empty"; + } + + @Override + public String displayKey() + { + return GridFilterLocalizationConfig.OP_IS_NOT_EMPTY; + } + + @Override + public boolean test(final Object input, final NoValue filterValue) + { + if(input instanceof final String s) + { + return !s.isEmpty(); + } + + return input != null; + } +}