├── .github
├── dependabot.yml
└── workflows
│ ├── IJ-latest.yml
│ ├── IJ.yml
│ ├── ci.yml
│ ├── conventionalCheck.yml
│ ├── nightly.yml
│ └── release.yml
├── .gitignore
├── CONTRIBUTING.md
├── DCO
├── LICENSE
├── README.md
├── USAGE_DATA.md
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── consumer-preferences.png
├── optin-preferences.png
└── optin-request.png
├── settings.gradle.kts
└── src
├── main
├── java
│ └── com
│ │ └── redhat
│ │ └── devtools
│ │ └── intellij
│ │ └── telemetry
│ │ ├── core
│ │ ├── configuration
│ │ │ ├── AbstractConfiguration.java
│ │ │ ├── ClasspathConfiguration.java
│ │ │ ├── CompositeConfiguration.java
│ │ │ ├── FileConfiguration.java
│ │ │ ├── IConfiguration.java
│ │ │ ├── SaveableFileConfiguration.java
│ │ │ ├── SystemProperties.java
│ │ │ ├── TelemetryConfiguration.java
│ │ │ └── limits
│ │ │ │ ├── Enabled.java
│ │ │ │ ├── EventCounts.java
│ │ │ │ ├── EventLimits.java
│ │ │ │ ├── Filter.java
│ │ │ │ ├── IEventLimits.java
│ │ │ │ ├── LimitsConfigurations.java
│ │ │ │ ├── PluginLimits.java
│ │ │ │ └── PluginLimitsDeserialization.java
│ │ ├── service
│ │ │ ├── Application.java
│ │ │ ├── Country.java
│ │ │ ├── Environment.java
│ │ │ ├── Event.java
│ │ │ ├── FeedbackService.java
│ │ │ ├── FeedbackServiceFactory.java
│ │ │ ├── IDE.java
│ │ │ ├── IMessageBroker.java
│ │ │ ├── IService.java
│ │ │ ├── Message.java
│ │ │ ├── Platform.java
│ │ │ ├── Plugin.java
│ │ │ ├── TelemetryMessageBuilder.java
│ │ │ ├── TelemetryService.java
│ │ │ ├── TelemetryServiceFactory.java
│ │ │ ├── UserId.java
│ │ │ └── segment
│ │ │ │ ├── ISegmentConfiguration.java
│ │ │ │ ├── IdentifyTraits.java
│ │ │ │ ├── IdentifyTraitsPersistence.java
│ │ │ │ ├── SegmentBroker.java
│ │ │ │ ├── SegmentBrokerFactory.java
│ │ │ │ ├── SegmentConfiguration.java
│ │ │ │ └── package-info.java
│ │ └── util
│ │ │ ├── AnonymizeUtils.java
│ │ │ ├── BasicGlobPattern.java
│ │ │ ├── CircularBuffer.java
│ │ │ ├── Directories.java
│ │ │ ├── FileUtils.java
│ │ │ ├── LICENCE
│ │ │ ├── Lazy.java
│ │ │ ├── MapBuilder.java
│ │ │ └── TimeUtils.java
│ │ └── ui
│ │ ├── TelemetryNotifications.java
│ │ ├── preferences
│ │ ├── TelemetryComponent.java
│ │ ├── TelemetryConfigurable.java
│ │ └── TelemetryPreferencesUtils.java
│ │ └── utils
│ │ ├── JBLabelUtils.java
│ │ └── NotificationGroupFactory.java
└── resources
│ ├── META-INF
│ └── plugin.xml
│ ├── segment-defaults.properties
│ ├── telemetry-config.json
│ └── timezones.json
└── test
├── java
└── com
│ └── redhat
│ └── devtools
│ └── intellij
│ └── telemetry
│ ├── core
│ ├── configuration
│ │ ├── ClasspathConfigurationTest.java
│ │ ├── FileConfigurationTest.java
│ │ ├── SaveableFileConfigurationTest.java
│ │ ├── TelemetryConfigurationTest.java
│ │ └── limits
│ │ │ ├── EventCountsTest.java
│ │ │ ├── EventLimitsTest.java
│ │ │ ├── EventNameFilterTest.java
│ │ │ ├── EventPropertyFilterTest.java
│ │ │ ├── LimitsConfigurationsIntegrationTest.java
│ │ │ ├── Mocks.java
│ │ │ ├── PluginLimitsDeserializationTest.java
│ │ │ ├── PluginLimitsRatioTests.java
│ │ │ └── PluginLimitsTest.java
│ ├── service
│ │ ├── CountryTest.java
│ │ ├── EnvironmentTest.java
│ │ ├── Fakes.java
│ │ ├── TelemetryMessageBuilderIntegrationTest.java
│ │ ├── TelemetryMessageBuilderTest.java
│ │ ├── TelemetryServiceTest.java
│ │ ├── UserIdTest.java
│ │ └── segment
│ │ │ ├── IdentifyTraitsPersistenceTest.java
│ │ │ ├── SegmentBrokerTest.java
│ │ │ └── SegmentConfigurationTest.java
│ └── util
│ │ ├── AnonymizeUtilsTest.java
│ │ ├── BasicGlobPatternTest.java
│ │ └── ConfigurationUtils.java
│ └── util
│ ├── BlockingFlush.java
│ └── StdOutLogging.java
└── resources
├── segment-defaults.properties
└── segment.properties
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # Set update schedule for GitHub Actions and Gradle dependencies
2 |
3 | version: 2
4 | updates:
5 | # Updates for GitHub Actions
6 | - package-ecosystem: "github-actions"
7 | directory: "/"
8 | schedule:
9 | interval: "weekly"
10 | # Updates for Gradle dependencies
11 | - package-ecosystem: "gradle"
12 | directory: "/"
13 | schedule:
14 | interval: "weekly"
--------------------------------------------------------------------------------
/.github/workflows/IJ-latest.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Latest IJ
5 |
6 | on:
7 | schedule:
8 | - cron: "0 0 * * *"
9 |
10 | jobs:
11 |
12 | build:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 | - name: Set up JDK 17
18 | uses: actions/setup-java@v4
19 | with:
20 | java-version: 17
21 | distribution: 'temurin'
22 | cache: 'gradle'
23 | - name: Grant execute permission for gradlew
24 | run: chmod +x gradlew
25 | - name: Build with Gradle
26 | run: |
27 | LATEST_EAP_SNAPSHOT=$(./gradlew printProductsReleases | grep 'IC-' | head -n 1 | cut -d'-' -f2)
28 | ./gradlew build --continue -PideaVersion=$LATEST_EAP_SNAPSHOT
29 | - uses: actions/upload-artifact@v4
30 | if: always()
31 | with:
32 | name: test-reports
33 | path: |
34 | build/test-results/**/*.xml
35 |
--------------------------------------------------------------------------------
/.github/workflows/IJ.yml:
--------------------------------------------------------------------------------
1 | name: Validate against IJ versions
2 | on:
3 | # Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests)
4 | push:
5 | branches: [ main ]
6 | # Trigger the workflow on any pull request
7 | pull_request:
8 |
9 | jobs:
10 | # Run plugin structure verification along with IntelliJ Plugin Verifier
11 | verify:
12 | name: Verify plugin
13 | runs-on: ubuntu-latest
14 | steps:
15 |
16 | # Free GitHub Actions Environment Disk Space
17 | - name: Maximize Build Space
18 | uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # main
19 | with:
20 | tool-cache: false
21 | large-packages: false
22 |
23 | # Check out current repository
24 | - name: Fetch Sources
25 | uses: actions/checkout@v4
26 |
27 | # Set up Java environment for the next steps
28 | - name: Setup Java
29 | uses: actions/setup-java@v4
30 | with:
31 | distribution: 'temurin'
32 | java-version: 17
33 | cache: 'gradle'
34 |
35 | # Setup Gradle
36 | - name: Setup Gradle
37 | uses: gradle/actions/setup-gradle@v4
38 |
39 | # Run Verify Plugin task and IntelliJ Plugin Verifier tool
40 | - name: Run Plugin Verification tasks
41 | run: |
42 | ./gradlew verifyPlugin -Dplugin.verifier.home.dir=~/.pluginVerifier
43 | REPORTS=$(cat ${{ github.workspace }}/build/reports/pluginVerifier/*/report.md | sed 's/^#/##/')
44 | echo "$REPORTS" >> $GITHUB_STEP_SUMMARY
45 |
46 | # Collect Plugin Verifier Result
47 | - name: Collect Plugin Verifier Result
48 | if: ${{ always() }}
49 | uses: actions/upload-artifact@v4
50 | with:
51 | name: pluginVerifier-result
52 | path: ${{ github.workspace }}/build/reports/pluginVerifier
53 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Gradle
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
3 |
4 | name: Java CI with Gradle
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | build-linux:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v4
18 | - name: Set up JDK 17
19 | uses: actions/setup-java@v4
20 | with:
21 | java-version: 17
22 | distribution: 'temurin'
23 | cache: 'gradle'
24 | - name: Grant execute permission for gradlew
25 | run: chmod +x gradlew
26 | - name: Build with Gradle
27 | run: ./gradlew build
28 | - uses: actions/upload-artifact@v4
29 | if: always()
30 | with:
31 | name: linux-test-reports
32 | path: build/reports
33 |
34 | build-macos:
35 | runs-on: macos-latest
36 |
37 | steps:
38 | - uses: actions/checkout@v4
39 | - name: Set up JDK 17
40 | uses: actions/setup-java@v4
41 | with:
42 | java-version: 17
43 | distribution: 'temurin'
44 | cache: 'gradle'
45 | - name: Grant execute permission for gradlew
46 | run: chmod +x gradlew
47 | - name: Build with Gradle
48 | run: ./gradlew build
49 | - uses: actions/upload-artifact@v4
50 | if: always()
51 | with:
52 | name: macos-test-reports
53 | path: build/reports
54 |
55 | build-windows:
56 | runs-on: windows-latest
57 |
58 | steps:
59 | - uses: actions/checkout@v4
60 | - name: Set up JDK 17
61 | uses: actions/setup-java@v4
62 | with:
63 | java-version: 17
64 | distribution: 'temurin'
65 | cache: 'gradle'
66 | - name: Grant execute permission for gradlew
67 | run: chmod +x gradlew
68 | - name: Build with Gradle
69 | run: ./gradlew build
70 | - uses: actions/upload-artifact@v4
71 | if: always()
72 | with:
73 | name: windows-test-reports
74 | path: build/reports
--------------------------------------------------------------------------------
/.github/workflows/conventionalCheck.yml:
--------------------------------------------------------------------------------
1 | name: "Conventional Commits PR Check"
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | jobs:
11 | main:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 #v5.5.3
15 | env:
16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/nightly.yml:
--------------------------------------------------------------------------------
1 | name: Nightly Build
2 | on:
3 | schedule:
4 | - cron: '0 1 * * *'
5 | workflow_dispatch:
6 |
7 | jobs:
8 | should-build-change:
9 | runs-on: ubuntu-latest
10 | outputs:
11 | repo-cache-hit: ${{ steps.cache-last-commit.outputs.cache-hit }}
12 | steps:
13 | - name: Fetch Sources
14 | uses: actions/checkout@v4
15 | - run: |
16 | git rev-parse HEAD >> lastCommit
17 | - name: Check New Changes
18 | id: cache-last-commit
19 | uses: actions/cache@v4
20 | with:
21 | path: lastCommit
22 | key: lastCommit-${{ hashFiles('lastCommit') }}
23 |
24 | # Prepare and publish the plugin to JetBrains Marketplace repository
25 | pre-release:
26 | needs: should-build-change
27 | if: ${{ needs.should-build-change.outputs.repo-cache-hit != 'true' || github.event_name != 'schedule' }}
28 | name: Publish Plugin
29 | runs-on: ubuntu-latest
30 | permissions:
31 | contents: write
32 | pull-requests: write
33 | steps:
34 | # Check out current repository
35 | - name: Fetch Sources
36 | uses: actions/checkout@v4
37 |
38 | # Set up Java environment for the next steps
39 | - name: Setup Java
40 | uses: actions/setup-java@v4
41 | with:
42 | java-version: 17
43 | distribution: 'temurin'
44 | cache: 'gradle'
45 |
46 | # Setup Gradle
47 | - name: Setup Gradle
48 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
49 | with:
50 | add-job-summary: 'on-failure'
51 | add-job-summary-as-pr-comment: 'on-failure'
52 | validate-wrappers: true
53 | gradle-home-cache-cleanup: true
54 |
55 | - name: Publish Plugin
56 | env:
57 | PUBLISH_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
58 | run: |
59 | CURRENT_VERSION=$(grep "projectVersion=" gradle.properties | cut -d'=' -f2)
60 | BASE_VERSION=${CURRENT_VERSION%-SNAPSHOT}
61 | TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
62 | PLUGIN_VERSION="${BASE_VERSION}-${TIMESTAMP}"
63 | ./gradlew publishPlugin -PjetBrainsToken=$PUBLISH_TOKEN -PprojectVersion=${PLUGIN_VERSION} -PjetBrainsChannel=nightly
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release Plugin
2 | run-name: Release ${{ inputs.release_version }}
3 |
4 | #Only one job at a time
5 | concurrency: release
6 |
7 | on:
8 | workflow_dispatch:
9 | inputs:
10 | publishToMarketPlace:
11 | description: 'Publish to JetBrains Marketplace ?'
12 | required: true
13 | type: choice
14 | options:
15 | - 'true'
16 | - 'false'
17 | default: 'false'
18 | release_version:
19 | description: 'Release version'
20 | required: true
21 | type: string
22 |
23 | jobs:
24 | # Prepare and publish the plugin to JetBrains Marketplace repository
25 | release:
26 | name: Publish new release
27 | runs-on: ubuntu-latest
28 | permissions:
29 | contents: write
30 | pull-requests: write
31 | steps:
32 | # Check out current repository
33 | - name: Fetch Sources
34 | uses: actions/checkout@v4
35 |
36 | # Set up Java environment for the next steps
37 | - name: Setup Java
38 | uses: actions/setup-java@v4
39 | with:
40 | distribution: 'temurin'
41 | java-version: 17
42 |
43 | # Setup Gradle
44 | - name: Setup Gradle
45 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
46 | with:
47 | add-job-summary: 'on-failure'
48 | add-job-summary-as-pr-comment: 'on-failure'
49 | validate-wrappers: true
50 | gradle-home-cache-cleanup: true
51 |
52 | - name: Tag Release
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | run: |
56 | git config user.email "action@github.com"
57 | git config user.name "GitHub Action"
58 | if git diff --quiet; then
59 | echo "No changes to commit."
60 | else
61 | git commit -sam "chore(skip-release): set version to ${{ inputs.release_version }}"
62 | fi
63 | git tag ${{ inputs.release_version }}
64 | git push origin ${{ inputs.release_version }}
65 |
66 | - name: Set Release Version
67 | shell: bash
68 | run: |
69 | CURRENT_VERSION=$(grep "projectVersion=" gradle.properties | cut -d'=' -f2)
70 | NEW_VERSION=${{ inputs.release_version }}.${{ github.run_number }}
71 | awk -v current="$CURRENT_VERSION" -v new="$NEW_VERSION" 'BEGIN {FS=OFS="="} $1 == "projectVersion" { $2 = new }1' gradle.properties > tmpfile && mv tmpfile gradle.properties
72 | echo "Release version: $NEW_VERSION"
73 | echo "PLUGIN_VERSION=${NEW_VERSION}" >> $GITHUB_ENV
74 |
75 | # Publish the plugin to JetBrains Marketplace
76 | - name: Publish Plugin to JetBrains Marketplace
77 | if: ${{ inputs.publishToMarketPlace == 'true' }}
78 | env:
79 | PUBLISH_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }}
80 | run: |
81 | ./gradlew publishPlugin -PjetBrainsToken=$PUBLISH_TOKEN -PprojectVersion=$PLUGIN_VERSION -PjetBrainsChannel=stable
82 | echo "Published $PLUGIN_VERSION to the Jetbrains Marketplace"
83 |
84 | # Set next SNAPSHOT version
85 | - name: Increment Plugin Version
86 | shell: bash
87 | env:
88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
89 | run: |
90 | CURRENT_VERSION=$(grep "projectVersion=" gradle.properties | cut -d'=' -f2)
91 | IFS="-" read -ra VERSION_PARTS <<< "$CURRENT_VERSION"
92 | IFS="." read -ra VERSION_NUM <<< "${VERSION_PARTS[0]}"
93 | ((VERSION_NUM[2]+=1))
94 | NEW_VERSION="${VERSION_NUM[0]}.${VERSION_NUM[1]}.${VERSION_NUM[2]}-SNAPSHOT"
95 | awk -v new_version="$NEW_VERSION" '/projectVersion=/{sub(/=.*/, "=" new_version)}1' gradle.properties > tmpfile && mv tmpfile gradle.properties
96 | echo "Set $NEW_VERSION in gradle.properties"
97 | git checkout -b $NEW_VERSION
98 | git commit -sam "chore(skip-release): set version to $NEW_VERSION"
99 | git push -u origin $NEW_VERSION
100 | gh pr create -B main -H $NEW_VERSION --title "chore(skip-release): set version to $NEW_VERSION" --body 'Created by Github action'
101 |
102 | - name: Simple conventional changelog
103 | uses: redhat-developer/simple-conventional-changelog@0a6db1ac3910c2cf66f2e1a530951dba1ece8540 #0.0.12
104 | id: changelog
105 | with:
106 | token: ${{ secrets.GITHUB_TOKEN }}
107 | current-tag: ${{ inputs.release_version }}
108 | types-mapping: 'feat:Features,fix:Bug Fixes,docs:Documentation,refactor:Refactoring,chore:Other'
109 |
110 | # Create a new GitHub release
111 | - name: Create Github Release
112 | env:
113 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
114 | run: |
115 | gh release create ${{ inputs.release_version }} \
116 | --title "${{ inputs.release_version }}" \
117 | --notes "$(cat << 'EOM'
118 | ${{ steps.changelog.outputs.changelog }}
119 | EOM
120 | )"
121 |
122 | - name: Upload Release Asset
123 | if: ${{ inputs.publishToMarketPlace == 'true' }}
124 | env:
125 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
126 | run: gh release upload ${{ inputs.release_version }} ./build/distributions/*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | .idea
3 | *.iml
4 | build
5 | .gradle
6 | out
7 | /.intellijPlatform/
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Contributing
2 |
3 | ### Certificate of Origin
4 |
5 | By contributing to this project you agree to the Developer Certificate of
6 | Origin (DCO). This document was created by the Linux Kernel community and is a
7 | simple statement that you, as a contributor, have the legal right to make the
8 | contribution. See the [DCO](DCO) file for details.
9 |
--------------------------------------------------------------------------------
/DCO:
--------------------------------------------------------------------------------
1 | Developer Certificate of Origin
2 | Version 1.1
3 |
4 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
5 | 1 Letterman Drive
6 | Suite D4700
7 | San Francisco, CA, 94129
8 |
9 | Everyone is permitted to copy and distribute verbatim copies of this
10 | license document, but changing it is not allowed.
11 |
12 |
13 | Developer's Certificate of Origin 1.1
14 |
15 | By making a contribution to this project, I certify that:
16 |
17 | (a) The contribution was created in whole or in part by me and I
18 | have the right to submit it under the open source license
19 | indicated in the file; or
20 |
21 | (b) The contribution is based upon previous work that, to the best
22 | of my knowledge, is covered under an appropriate open source
23 | license and I have the right under that license to submit that
24 | work with modifications, whether created in whole or in part
25 | by me, under the same open source license (unless I am
26 | permitted to submit under a different license), as indicated
27 | in the file; or
28 |
29 | (c) The contribution was provided directly to me by some other
30 | person who certified (a), (b) or (c) and I have not modified
31 | it.
32 |
33 | (d) I understand and agree that this project and the contribution
34 | are public and that a record of the contribution (including all
35 | personal information I submit with it, including my sign-off) is
36 | maintained indefinitely and may be redistributed consistent with
37 | this project or the open source license(s) involved.
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Red Hat Telemetry
2 | [plugin-repo]: https://plugins.jetbrains.com/plugin/16209-telemetry-by-red-hat
3 | [plugin-version-svg]: https://img.shields.io/jetbrains/plugin/v/16209-telemetry-by-red-hat.svg
4 | [plugin-downloads-svg]: https://img.shields.io/jetbrains/plugin/d/16209-telemetry-by-red-hat.svg
5 |
6 | [](https://github.com/redhat-developer/intellij-redhat-telemetry/actions?query=workflow%3A%22Java+CI+with+Gradle%22)
7 | [![JetBrains plugins][plugin-version-svg]][plugin-repo]
8 | [![JetBrains plugins][plugin-downloads-svg]][plugin-repo]
9 |
10 | This library provides Telemetry APIs specifically meant to be used by IDEA plugins developed by Red Hat.
11 | ## Telemetry reporting
12 | With your approval, plugins published by Red Hat collect anonymous
13 | [usage data](https://github.com/redhat-developer/intellij-redhat-telemetry/blob/master/USAGE_DATA.md)
14 | and send it to Red Hat servers to help improve our products and services. Read our
15 | [privacy statement](https://developers.redhat.com/article/tool-data-collection) to learn more about it.
16 |
17 | The first time one of Red Hat plugins is engaging in telemetry collection runs, you will be asked to opt-in Red Hat's
18 | telemetry collection program:
19 |
20 | 
21 |
22 | Whether you accept or deny the request, this pop up will not show again.
23 | If you choose Later, you'll get asked once you restart your IDE.
24 |
25 | You can also opt-in later, by enabling it in the preferences at Tools > Red Hat Telemetry.
26 | 
27 |
28 | This will enable all telemetry events from Red Hat plugins going forward.
29 |
30 | ## How to disable telemetry reporting?
31 | If you want to stop sending usage data to Red Hat, you can disable it in the preferences at Tools > Red Hat Telemetry.
32 | This will silence all telemetry events from Red Hat plugins going forward.
33 |
34 | ## How to use from a IDEA plugin extension?
35 |
36 | ### Dependency
37 | Start by adding `com.redhat.devtools.intellij.telemetry` to the plugins section of your build.gradle, so that the dependency can be automatically downloaded and installed, when installing your extension from the Marketplace.
38 | ```groovy
39 | plugins "com.redhat.devtools.intellij.telemetry:0.0.1.9"
40 | ```
41 |
42 | ### [Optional] Add custom segment keys in src/main/resources/segment.properties
43 | By default, extensions will send their data to https://app.segment.com/redhat-devtools/sources/intellij/.
44 | In development mode, the data is sent to https://app.segment.com/redhat-devtools/sources/intellij_test/.
45 | You can specify custom segment keys to connect and push usage data to https://segment.com/
46 | ```properties
47 | "writeKey": "your-segment-key-goes-here",
48 | "debugWriteKey": "your-segment-key-goes-here-for-dev-mode",
49 | ```
50 |
51 | ### Create a Telemetry singleton
52 | You then need to create a singleton that allows you to get hold of the telemetry service.
53 | Lazy initialisation allows late construction when things are needed.
54 |
55 | ```java
56 | public class TelemetryService {
57 |
58 | private static final TelemetryService INSTANCE = new TelemetryService();
59 |
60 | private final Lazy builder = new Lazy<>(() -> new TelemetryMessageBuilder(TelemetryService.class.getClassLoader()));
61 |
62 | public static TelemetryMessageBuilder instance() {
63 | return INSTANCE.builder.get();
64 | }
65 | }
66 | ```
67 | ### Send a message
68 |
69 | To report an event you first need to get a hold on telemetry singleton and start by telling it what action you're about to build.
70 | Good practice suggests prefixing your action by a group name that you separate with a "-".
71 | Colons (":") cannot be used due to limitations in wooopra which won't automatically create a schema for your message in that case.
72 | ```java
73 | ActionMessage telemetry = TelemetryService.instance().action("smurfs-find the magic cauldron");
74 | ```
75 | You can then chain properties with their values to the point where you can send the message.
76 | ```java
77 | telemetry
78 | .property("kindness", "smurfette")
79 | .property("magic", "papa smurf")
80 | .send();
81 | ```
82 |
83 | ### Send special properties
84 | The telemetry plugin tracks the startup and shutdown of your plugin automatically.
85 | There's no need for you to send those messages, it's all done for you behind the scene.
86 |
87 | Success may be used to indicate particular outcomes.
88 | ```java
89 | telemetry.success("found the magic cauldron");
90 | ```
91 | Errors are passed along similarly. Error and success are both mutually exclusive.
92 | ```java
93 | telemetry.error("Gargamel was there");
94 | ```
95 | A duration may be provided to indicate how long an operation took. You start by signaling when the action started.
96 | ```java
97 | telemetry.started();
98 | ```
99 | Once the action is finished you provide the end time to the message.
100 | ```java
101 | telemetry.finished(LocalDateTime.now());
102 | ```
103 | Not providing it won't harm, it'll be done automatically for you.
104 |
105 | ### Retrieve the anonymous User Id
106 | Each message sends an anonymous user id along with the other payloads.
107 | This type 4 UUID is automatically created and stored in a file at `~/.redhat/anonymousId`
108 | To retrieve it you can query the class `UserId`.
109 | ```java
110 | String userId = UserId.INSTANCE.get();
111 | ```
112 |
113 | ### Add a link to telemetry preferences
114 | Telemetry provides a label that you can add to your plugin preferences panel.
115 | ```java
116 | JPanel preferences = FormBuilder.createFormBuilder()
117 | .addComponent(TelemetryPreferencesUtils.createTelemetryComponent("Tekton Pipelines", this::getPanel), 1)
118 | .addComponentFillVertically(new JPanel(), 0)
119 | .getPanel();
120 | ```
121 | It provides a link that the user can click to get to the telemetry preferences.
122 |
123 | 
--------------------------------------------------------------------------------
/USAGE_DATA.md:
--------------------------------------------------------------------------------
1 | ## Usage data being collected by Red Hat Plugins
2 | Only anonymous data is being collected by Red Hat plugins leveraging 'Red Hat Telemetry' facilities.
3 | The IP address of telemetry requests is not even stored on Red Hat servers.
4 |
5 | ### Common data
6 | Telemetry requests may contain:
7 |
8 | * a random anonymous user id (UUID v4), that is stored locally in `~/.redhat/anonymousId`
9 | * the client name (IntelliJ IDEA), its version and the java runtime that runs it
10 | * the name and version of the plugin that sends the event (eg. `Tekton Pipelines by Red Hat`)
11 | * the OS name and version
12 | * the user locale (eg. en-US)*
13 | * the user timezone
14 | * the country id (as determined by the current timezone)
15 |
16 | Common events are reported:
17 |
18 | * when the plugin is started
19 | * when the plugin is shut down
20 | - duration of the session
21 |
22 | ### Other plugins
23 | Red Hat plugins specific telemetry collection details can be found there:
24 |
25 | * [Tekton Pipelines by Red Hat](https://github.com/redhat-developer/intellij-tekton/blob/master/USAGE_DATA.md)
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
2 | import org.jetbrains.intellij.platform.gradle.TestFrameworkType
3 | import org.jetbrains.intellij.platform.gradle.models.ProductRelease
4 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.VerificationReportsFormats.*
5 | import org.jetbrains.intellij.platform.gradle.tasks.VerifyPluginTask.FailureLevel.*
6 |
7 | plugins {
8 | alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin
9 | id("idea")
10 | }
11 |
12 | group = "com.redhat.devtools.intellij"
13 | version = providers.gradleProperty("projectVersion").get() // Plugin version
14 | val platformVersion = providers.gradleProperty("ideaVersion").get()
15 | val javaVersion = 17
16 |
17 | // https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-tasks.html#runIdeForUiTests
18 | val runIdeForUiTests by intellijPlatformTesting.runIde.registering {
19 | task {
20 | jvmArgumentProviders += CommandLineArgumentProvider {
21 | listOf(
22 | "-Dide.mac.message.dialogs.as.sheets=false",
23 | "-Djb.privacy.policy.text=",
24 | "-Djb.consents.confirmation.enabled=false",
25 | "-Duser.language=en -Duser.country=US"
26 | )
27 | }
28 |
29 | systemProperty("robot-server.port", System.getProperty("robot-server.port"))
30 | systemProperties["com.redhat.devtools.intellij.telemetry.mode"] = "debug"
31 | }
32 |
33 | plugins {
34 | robotServerPlugin()
35 | }
36 | }
37 |
38 | intellijPlatform {
39 |
40 | pluginConfiguration {
41 | ideaVersion {
42 | sinceBuild = providers.gradleProperty("sinceIdeaBuild")
43 | untilBuild = provider { null }
44 | }
45 | }
46 |
47 | publishing {
48 | token = providers.gradleProperty("jetBrainsToken")
49 | channels = providers.gradleProperty("jetBrainsChannel").map { listOf(it) }
50 | }
51 |
52 | pluginVerification {
53 | failureLevel = listOf(INVALID_PLUGIN, COMPATIBILITY_PROBLEMS, MISSING_DEPENDENCIES)
54 | verificationReportsFormats = listOf(MARKDOWN, PLAIN)
55 | ides {
56 | recommended()
57 | }
58 | freeArgs = listOf(
59 | "-mute",
60 | "TemplateWordInPluginId,TemplateWordInPluginName"
61 | )
62 | }
63 | }
64 |
65 | dependencies {
66 | intellijPlatform {
67 | create(IntelliJPlatformType.IntellijIdeaCommunity, platformVersion)
68 |
69 | pluginVerifier()
70 |
71 | testFramework(TestFrameworkType.Platform)
72 | }
73 | implementation(libs.analytics) {
74 | exclude(group = "org.jetbrains.kotlin")
75 | exclude(group = "com.squareup.retrofit2", module = "retrofit-mock")
76 | exclude(group = "com.google.auto.value", module = "auto-value-annotations")
77 | }
78 | implementation(libs.okio) {
79 | exclude(group = "org.jetbrains.kotlin")
80 | }
81 | implementation(libs.gson)
82 |
83 | testImplementation(libs.junit)
84 | testImplementation(libs.junit.jupiter)
85 | testImplementation(libs.junit.platform.launcher)
86 | testImplementation(libs.assertj.core)
87 | testImplementation(libs.mockito.core)
88 |
89 | testRuntimeOnly(libs.junit.jupiter.engine)
90 | }
91 |
92 | val platformTests by intellijPlatformTesting.testIde.registering {
93 | task {
94 | // Discover and execute JUnit4-based EventCountsTest
95 | useJUnit()
96 | description = "Runs the platform tests."
97 | group = "verification"
98 | outputs.upToDateWhen { false }
99 | mustRunAfter(tasks["test"])
100 | }
101 | }
102 |
103 | tasks {
104 | wrapper {
105 | gradleVersion = providers.gradleProperty("gradleVersion").get()
106 | }
107 |
108 | runIde {
109 | systemProperty("com.redhat.devtools.intellij.telemetry.mode", "debug")
110 | }
111 |
112 | test {
113 | useJUnitPlatform()
114 | }
115 |
116 | printProductsReleases {
117 | channels = listOf(ProductRelease.Channel.EAP)
118 | types = listOf(IntelliJPlatformType.IntellijIdeaCommunity)
119 | untilBuild = provider { null }
120 | }
121 | }
122 |
123 | configurations.all {
124 | exclude(group = "org.slf4j", module = "slf4j-api")
125 | }
126 |
127 | java {
128 | toolchain {
129 | languageVersion = JavaLanguageVersion.of(javaVersion)
130 | }
131 | withSourcesJar()
132 | }
133 |
134 | repositories {
135 | mavenLocal()
136 | mavenCentral()
137 | intellijPlatform {
138 | defaultRepositories()
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | projectVersion=1.2.2-SNAPSHOT
2 | jetBrainsToken=invalid
3 | jetBrainsChannel=stable
4 | nexusUser=invalid
5 | nexusPassword=invalid
6 |
7 | # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
8 | sinceIdeaBuild=231
9 |
10 | # IntelliJ Platform Properties -> https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#configuration-intellij-extension
11 | ideaVersion=2024.3
12 |
13 | # Gradle Releases -> https://github.com/gradle/gradle/releases
14 | gradleVersion=8.5
15 |
16 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
17 | kotlin.stdlib.default.dependency=false
18 |
19 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
20 | org.gradle.configuration-cache=true
21 |
22 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
23 | org.gradle.caching=true
24 |
25 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | # libraries
3 | junit = "4.13.2"
4 | assertj-core = "3.22.0"
5 | mockito-core = "5.14.2"
6 | junit-platform = "1.10.3"
7 | junit-jupiter = "5.10.3"
8 | gson = "2.10.1"
9 | analytics = "3.4.0"
10 | okio = "3.6.0"
11 |
12 | # plugins
13 | gradleIntelliJPlugin = "2.2.1"
14 |
15 | [libraries]
16 | junit = { group = "junit", name = "junit", version.ref = "junit" }
17 | assertj-core = { group = "org.assertj", name = "assertj-core", version.ref = "assertj-core" }
18 | mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito-core" }
19 | junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junit-platform" }
20 | junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit-jupiter" }
21 | junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit-jupiter" }
22 | gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
23 | analytics = { group = "com.segment.analytics.java", name = "analytics", version.ref = "analytics" }
24 | okio = { group = "com.squareup.okio", name = "okio", version.ref = "okio" }
25 |
26 | [plugins]
27 | gradleIntelliJPlugin = { id = "org.jetbrains.intellij.platform", version.ref = "gradleIntelliJPlugin" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redhat-developer/intellij-redhat-telemetry/fde3e487962930e36d36282199f216c77d090020/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/images/consumer-preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redhat-developer/intellij-redhat-telemetry/fde3e487962930e36d36282199f216c77d090020/images/consumer-preferences.png
--------------------------------------------------------------------------------
/images/optin-preferences.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redhat-developer/intellij-redhat-telemetry/fde3e487962930e36d36282199f216c77d090020/images/optin-preferences.png
--------------------------------------------------------------------------------
/images/optin-request.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redhat-developer/intellij-redhat-telemetry/fde3e487962930e36d36282199f216c77d090020/images/optin-request.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "intellij-redhat-telemetry"
2 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/AbstractConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.util.Lazy;
14 |
15 | import java.util.Properties;
16 |
17 | public abstract class AbstractConfiguration implements IConfiguration {
18 |
19 | protected final Lazy properties = new Lazy<>(this::loadProperties);
20 |
21 | @Override
22 | public String get(String key) {
23 | return properties.get().getProperty(key);
24 | }
25 |
26 | @Override
27 | public void put(String key, String value) {
28 | properties.get().put(key, value);
29 | }
30 |
31 | protected abstract Properties loadProperties();
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import java.io.InputStream;
14 | import java.nio.file.Path;
15 |
16 | public class ClasspathConfiguration extends FileConfiguration {
17 |
18 | private final ClassLoader classloader;
19 |
20 | public ClasspathConfiguration(Path file) {
21 | this(file, ClasspathConfiguration.class.getClassLoader());
22 | }
23 |
24 | public ClasspathConfiguration(Path file, ClassLoader classLoader) {
25 | super(file);
26 | this.classloader = classLoader;
27 | }
28 |
29 | @Override
30 | protected InputStream createInputStream(Path path) {
31 | if (path == null) {
32 | return null;
33 | }
34 | return classloader.getResourceAsStream(path.toString());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/CompositeConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import org.jetbrains.annotations.Nullable;
14 |
15 | import java.util.List;
16 | import java.util.Objects;
17 |
18 | public abstract class CompositeConfiguration implements IConfiguration {
19 |
20 | @Nullable
21 | @Override
22 | public String get(final String key) {
23 | List configurations = getConfigurations();
24 | if (configurations == null
25 | || configurations.isEmpty()) {
26 | return null;
27 | }
28 | return configurations.stream()
29 | .map(configuration -> configuration.get(key))
30 | .filter(Objects::nonNull)
31 | .findFirst()
32 | .orElse(null);
33 | }
34 |
35 | protected abstract List getConfigurations();
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.intellij.openapi.diagnostic.Logger;
14 |
15 | import java.io.File;
16 | import java.io.FileInputStream;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.nio.file.Path;
20 | import java.util.Properties;
21 |
22 | public class FileConfiguration extends AbstractConfiguration {
23 |
24 | private static final Logger LOGGER = Logger.getInstance(FileConfiguration.class);
25 |
26 | protected final Path path;
27 |
28 | public FileConfiguration(Path path) {
29 | this.path = path;
30 | }
31 |
32 | @Override
33 | protected Properties loadProperties() {
34 | Properties properties = new Properties();
35 | try (InputStream in = createInputStream(path)) {
36 | if (in != null) {
37 | properties.load(in);
38 | }
39 | } catch (IOException e) {
40 | LOGGER.warn("Could not load properties file " + (path == null? "" : path.toAbsolutePath()));
41 | }
42 | return properties;
43 | }
44 |
45 | protected InputStream createInputStream(Path path) throws IOException {
46 | if (path == null) {
47 | return null;
48 | }
49 | File file = path.toFile();
50 | if (!file.exists()) {
51 | return null;
52 | }
53 | return new FileInputStream(file);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/IConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | public interface IConfiguration {
14 | String get(String key);
15 | void put(String key, String value);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.intellij.openapi.diagnostic.Logger;
14 |
15 | import java.io.File;
16 | import java.io.FileWriter;
17 | import java.io.IOException;
18 | import java.io.Writer;
19 | import java.nio.file.Path;
20 | import java.time.LocalDate;
21 |
22 | public class SaveableFileConfiguration extends FileConfiguration {
23 |
24 | private static final Logger LOGGER = Logger.getInstance(SaveableFileConfiguration.class);
25 |
26 | public SaveableFileConfiguration(Path file) {
27 | super(file);
28 | }
29 |
30 | public void save() throws IOException {
31 | if (path == null) {
32 | return;
33 | }
34 | File file = path.toFile();
35 | file.createNewFile(); // ensure exists
36 | try (Writer writer = new FileWriter(file)) {
37 | properties.get().store(writer, "updated " + LocalDate.now());
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/SystemProperties.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import java.util.Properties;
14 |
15 | public class SystemProperties extends AbstractConfiguration {
16 |
17 | @Override
18 | protected Properties loadProperties() {
19 | return System.getProperties();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/TelemetryConfiguration.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.intellij.openapi.application.ApplicationManager;
14 | import com.intellij.util.messages.Topic;
15 | import com.redhat.devtools.intellij.telemetry.core.util.Directories;
16 |
17 | import java.io.IOException;
18 | import java.util.Arrays;
19 | import java.util.List;
20 |
21 | public class TelemetryConfiguration extends CompositeConfiguration {
22 |
23 | public static final String KEY_MODE = "com.redhat.devtools.intellij.telemetry.mode";
24 |
25 | private static final SaveableFileConfiguration FILE = new SaveableFileConfiguration(
26 | Directories.RED_HAT.resolve("com.redhat.devtools.intellij.telemetry"));
27 |
28 | private static final TelemetryConfiguration INSTANCE = new TelemetryConfiguration();
29 |
30 | public static TelemetryConfiguration getInstance() {
31 | return INSTANCE;
32 | }
33 |
34 | // for testing purposes
35 | protected TelemetryConfiguration() {
36 | }
37 |
38 | public void setMode(Mode mode) {
39 | put(KEY_MODE, mode.toString());
40 | }
41 |
42 | public Mode getMode() {
43 | return Mode.safeValueOf(get(KEY_MODE));
44 | }
45 |
46 | public boolean isEnabled() {
47 | return getMode().isEnabled();
48 | }
49 |
50 | public void setEnabled(boolean enabled) {
51 | setMode(Mode.valueOf(enabled));
52 | }
53 |
54 | public boolean isDebug() {
55 | return getMode() == Mode.DEBUG;
56 | }
57 |
58 | public boolean isConfigured() {
59 | return getMode().isConfigured();
60 | }
61 |
62 | @Override
63 | public void put(String key, String value) {
64 | getSaveableFile().put(key, value);
65 | getNotifier().configurationChanged(key, value);
66 | }
67 |
68 | protected ConfigurationChangedListener getNotifier() {
69 | return ApplicationManager.getApplication().getMessageBus()
70 | .syncPublisher(ConfigurationChangedListener.CONFIGURATION_CHANGED);
71 | }
72 |
73 | @Override
74 | protected List getConfigurations() {
75 | return Arrays.asList(
76 | new SystemProperties(),
77 | getSaveableFile());
78 | }
79 |
80 | public void save() throws IOException {
81 | getSaveableFile().save();
82 | }
83 |
84 | protected SaveableFileConfiguration getSaveableFile() {
85 | return FILE;
86 | }
87 |
88 | public enum Mode {
89 | NORMAL {
90 | @Override
91 | public boolean isEnabled() {
92 | return true;
93 | }
94 |
95 | @Override
96 | public boolean isConfigured() {
97 | return true;
98 | }
99 | }
100 | , DEBUG {
101 | @Override
102 | public boolean isEnabled() {
103 | return true;
104 | }
105 |
106 | @Override
107 | public boolean isConfigured() {
108 | return true;
109 | }
110 | }, DISABLED {
111 | @Override
112 | public boolean isEnabled() {
113 | return false;
114 | }
115 |
116 | @Override
117 | public boolean isConfigured() {
118 | return true;
119 | }
120 | }, UNKNOWN {
121 | @Override
122 | public boolean isEnabled() {
123 | return false;
124 | }
125 |
126 | @Override
127 | public boolean isConfigured() {
128 | return false;
129 | }
130 | };
131 |
132 | public abstract boolean isEnabled();
133 |
134 | public abstract boolean isConfigured();
135 |
136 | public static Mode safeValueOf(String value) {
137 | try {
138 | if (value == null) {
139 | return UNKNOWN;
140 | }
141 | return Mode.valueOf(value.toUpperCase());
142 | } catch (IllegalArgumentException e) {
143 | return UNKNOWN;
144 | }
145 | }
146 |
147 | public static Mode valueOf(boolean enabled) {
148 | if (enabled) {
149 | return Mode.NORMAL;
150 | } else {
151 | return Mode.DISABLED;
152 | }
153 | }
154 | }
155 |
156 | @FunctionalInterface
157 | public interface ConfigurationChangedListener {
158 | Topic CONFIGURATION_CHANGED =
159 | Topic.create("Telemetry Configuration Changed", ConfigurationChangedListener.class);
160 |
161 | void configurationChanged(String property, String value);
162 | }
163 |
164 | }
165 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/Enabled.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.intellij.openapi.util.text.StringUtil;
14 |
15 | import java.util.Arrays;
16 |
17 | public enum Enabled {
18 | ALL("all"),
19 | ERROR("error"),
20 | CRASH("crash"),
21 | OFF("off");
22 |
23 | private final String value;
24 |
25 | Enabled(String value) {
26 | this.value = value;
27 | }
28 |
29 | private boolean hasValue(String value) {
30 | if (StringUtil.isEmptyOrSpaces(value)) {
31 | return this.value == null;
32 | }
33 | return value.equals(this.value);
34 | }
35 |
36 | public static Enabled safeValueOf(String value) {
37 | return Arrays.stream(values())
38 | .filter(instance -> instance.hasValue(value))
39 | .findAny()
40 | .orElse(ALL);
41 | }
42 | }
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/EventCounts.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.intellij.openapi.application.ApplicationManager;
14 | import com.intellij.openapi.components.PersistentStateComponent;
15 | import com.intellij.openapi.components.Service;
16 | import com.intellij.openapi.components.State;
17 | import com.intellij.openapi.components.Storage;
18 | import com.intellij.openapi.util.text.StringUtil;
19 | import com.intellij.util.xmlb.XmlSerializerUtil;
20 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
21 | import org.jetbrains.annotations.NotNull;
22 | import org.jetbrains.annotations.Nullable;
23 |
24 | import java.time.LocalDateTime;
25 | import java.time.ZonedDateTime;
26 | import java.util.HashMap;
27 | import java.util.Map;
28 | import java.util.Objects;
29 |
30 | import static com.redhat.devtools.intellij.telemetry.core.util.TimeUtils.isToday;
31 |
32 | /**
33 | * A counter that stores daily occurrences of events.
34 | * The state is persisted and loaded by the IDEA platform
35 | *
36 | * @see PersistentStateComponent
37 | */
38 | @Service
39 | @State(
40 | name = " com.redhat.devtools.intellij.telemetry.core.configuration.limits.EventCounts",
41 | storages = @Storage(value = "eventCounts.xml")
42 | )
43 | public final class EventCounts implements PersistentStateComponent {
44 |
45 | public static EventCounts getInstance() {
46 | return ApplicationManager.getApplication().getService(EventCounts.class);
47 | }
48 |
49 | private static final String COUNT_VALUES_SEPARATOR = ",";
50 |
51 | EventCounts() {}
52 |
53 | public final Map counts = new HashMap<>();
54 |
55 | @Override
56 | public EventCounts getState() {
57 | return this;
58 | }
59 |
60 | @Override
61 | public void loadState(@NotNull EventCounts state) {
62 | XmlSerializerUtil.copyBean(state, this);
63 | }
64 |
65 | @Override
66 | public void noStateLoaded() {
67 | PersistentStateComponent.super.noStateLoaded();
68 | }
69 |
70 | @Override
71 | public void initializeComponent() {
72 | PersistentStateComponent.super.initializeComponent();
73 | }
74 |
75 | @Nullable
76 | public Count get(Event event) {
77 | if (event == null) {
78 | return null;
79 | }
80 | String countString = counts.get(event.getName());
81 | return toCount(countString);
82 | }
83 |
84 | public void put(Event event) {
85 | Count count = createOrUpdateCount(event);
86 | put(event, count);
87 | }
88 |
89 | EventCounts put(Event event, Count count) {
90 | if (event == null) {
91 | return this;
92 | }
93 | counts.put(event.getName(), toString(count));
94 | return this;
95 | }
96 |
97 | private Count createOrUpdateCount(Event event) {
98 | Count count = get(event);
99 | if (count != null) {
100 | // update existing
101 | count = count.newOccurrence();
102 | } else {
103 | // create new
104 | count = new Count();
105 | }
106 | return count;
107 | }
108 |
109 | private Count toCount(String string) {
110 | if (StringUtil.isEmpty(string)) {
111 | return null;
112 | }
113 | String[] split = string.split(COUNT_VALUES_SEPARATOR);
114 | LocalDateTime lastOccurrence = toLastOccurrence(split[0]);
115 | int total = toTotal(split[1]);
116 | return new Count(lastOccurrence, total);
117 | }
118 |
119 | private LocalDateTime toLastOccurrence(String value) {
120 | try {
121 | long epochSeconds = Long.parseLong(value);
122 | return LocalDateTime.ofEpochSecond(epochSeconds, 0, ZonedDateTime.now().getOffset());
123 | } catch (NumberFormatException e) {
124 | return null;
125 | }
126 | }
127 |
128 | private int toTotal(String value) {
129 | try {
130 | return Integer.parseInt(value);
131 | } catch (NumberFormatException e) {
132 | return 0;
133 | }
134 | }
135 |
136 | String toString(@NotNull Count count) {
137 | long epochSecond = count.lastOccurrence.toEpochSecond(ZonedDateTime.now().getOffset());
138 | return epochSecond + COUNT_VALUES_SEPARATOR + count.dailyTotal;
139 | }
140 |
141 | public static class Count {
142 | private final LocalDateTime lastOccurrence;
143 | private final int dailyTotal;
144 |
145 | Count() {
146 | this(LocalDateTime.now(), 1);
147 | }
148 |
149 | @Override
150 | public boolean equals(Object o) {
151 | if (this == o) return true;
152 | if (!(o instanceof Count)) return false;
153 | Count count = (Count) o;
154 | return dailyTotal == count.dailyTotal && Objects.equals(lastOccurrence, count.lastOccurrence);
155 | }
156 |
157 | @Override
158 | public int hashCode() {
159 | return Objects.hash(lastOccurrence, dailyTotal);
160 | }
161 |
162 | Count(LocalDateTime lastOccurrence, int dailyTotal) {
163 | this.lastOccurrence = lastOccurrence;
164 | this.dailyTotal = dailyTotal;
165 | }
166 |
167 | public LocalDateTime getLastOccurrence() {
168 | return lastOccurrence;
169 | }
170 |
171 | public int getDailyTotal() {
172 | if (isToday(lastOccurrence)) {
173 | return dailyTotal;
174 | } else {
175 | return 0;
176 | }
177 | }
178 |
179 | public Count newOccurrence() {
180 | return new Count(LocalDateTime.now(), getDailyTotal() + 1);
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/Filter.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
14 | import com.redhat.devtools.intellij.telemetry.core.util.BasicGlobPattern;
15 |
16 | public interface Filter {
17 |
18 | boolean isMatching(Event event);
19 |
20 | boolean isIncludedByRatio(float percentile);
21 |
22 | boolean isExcludedByRatio(float percentile);
23 |
24 | boolean isWithinDailyLimit(int total);
25 |
26 | class EventPropertyFilter implements Filter {
27 | private final String name;
28 | private final BasicGlobPattern glob;
29 |
30 | EventPropertyFilter(String name, String valueGlob) {
31 | this.name = name;
32 | this.glob = BasicGlobPattern.compile(valueGlob);
33 | }
34 |
35 | @Override
36 | public boolean isMatching(Event event) {
37 | String value = event.getProperties().get(name);
38 | return glob.matches(value);
39 | }
40 |
41 | @Override
42 | public boolean isIncludedByRatio(float percentile) {
43 | return true;
44 | }
45 |
46 | @Override
47 | public boolean isExcludedByRatio(float percentile) {
48 | return false;
49 | }
50 |
51 | @Override
52 | public boolean isWithinDailyLimit(int total) {
53 | return true;
54 | }
55 | }
56 |
57 | class EventNameFilter implements Filter {
58 |
59 | static final int DAILY_LIMIT_UNSPECIFIED = -1;
60 |
61 | private final BasicGlobPattern name;
62 | private final float ratio;
63 | private final int dailyLimit;
64 |
65 | EventNameFilter(String name, float ratio, int dailyLimit) {
66 | this.name = BasicGlobPattern.compile(name);
67 | this.ratio = ratio;
68 | this.dailyLimit = dailyLimit;
69 | }
70 |
71 | public float getRatio() {
72 | return ratio;
73 | }
74 |
75 | public int getDailyLimit() {
76 | return dailyLimit;
77 | }
78 |
79 | @Override
80 | public boolean isMatching(Event event) {
81 | return name.matches(event.getName());
82 | }
83 |
84 | @Override
85 | public boolean isIncludedByRatio(float percentile) {
86 | return ratio != 0
87 | && percentile <= ratio;
88 | }
89 |
90 | @Override
91 | public boolean isExcludedByRatio(float percentile) {
92 | return 1 - ratio < percentile;
93 | }
94 |
95 | @Override
96 | public boolean isWithinDailyLimit(int total) {
97 | if (dailyLimit == DAILY_LIMIT_UNSPECIFIED) {
98 | return true;
99 | } else {
100 | return total < dailyLimit; // at least 1 more to go
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/IEventLimits.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
14 |
15 | public interface IEventLimits {
16 |
17 | boolean canSend(Event event);
18 | void wasSent(Event event);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/LimitsConfigurations.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.intellij.openapi.diagnostic.Logger;
14 | import com.redhat.devtools.intellij.telemetry.core.util.Directories;
15 | import com.redhat.devtools.intellij.telemetry.core.util.FileUtils;
16 | import okhttp3.OkHttpClient;
17 | import okhttp3.Request;
18 | import okhttp3.Response;
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | import java.io.BufferedReader;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.io.InputStreamReader;
25 | import java.net.MalformedURLException;
26 | import java.net.URL;
27 | import java.nio.file.Files;
28 | import java.nio.file.Path;
29 | import java.nio.file.StandardCopyOption;
30 | import java.nio.file.attribute.FileTime;
31 | import java.util.concurrent.TimeUnit;
32 | import java.util.stream.Collectors;
33 |
34 | import static com.redhat.devtools.intellij.telemetry.core.util.FileUtils.ensureExists;
35 | import static com.redhat.devtools.intellij.telemetry.core.util.FileUtils.getPathForFileUrl;
36 |
37 | class LimitsConfigurations {
38 |
39 | private static final Logger LOGGER = Logger.getInstance(LimitsConfigurations.class);
40 | static final Path LOCAL = Directories.RED_HAT.resolve("telemetry-config.json");
41 | static final String EMBEDDED = "/telemetry-config.json";
42 | static final String REMOTE = "https://raw.githubusercontent.com/redhat-developer/intellij-redhat-telemetry/main/src/main/resources/telemetry-config.json";
43 | static final String SYSTEM_PROP_REMOTE = "REDHAT_TELEMETRY_REMOTE_CONFIG_URL";
44 |
45 | protected final OkHttpClient client = new OkHttpClient.Builder()
46 | .connectTimeout(5, TimeUnit.SECONDS)
47 | .readTimeout(5, TimeUnit.SECONDS)
48 | .writeTimeout(5, TimeUnit.SECONDS)
49 | .build();
50 |
51 | @Nullable
52 | public String downloadRemote() {
53 | String url = System.getProperty(SYSTEM_PROP_REMOTE);
54 |
55 | String content = readFile(url);
56 | if (content != null) {
57 | return content;
58 | }
59 |
60 | if (FileUtils.isFileUrl(url)
61 | || !isValidURL(url)) {
62 | // file-url to missing/unreadable file or missing/invalid url
63 | url = REMOTE;
64 | }
65 |
66 | return download(url);
67 | }
68 |
69 | private @Nullable String readFile(String url) {
70 | Path path = getPathForFileUrl(url);
71 | if (path == null) {
72 | return null;
73 | }
74 | try {
75 | return toString(Files.newInputStream(path));
76 | } catch (IOException e) {
77 | LOGGER.warn("Could not read remote limits configurations file from " + path, e);
78 | return null;
79 | }
80 | }
81 |
82 | @Nullable String download(String url) {
83 | Request request = new Request.Builder()
84 | .url(url)
85 | .addHeader("Content-Type", "application/json")
86 | .build();
87 | try (Response response = client.newCall(request).execute()) {
88 | if (response.body() != null) {
89 | Files.copy(response.body().byteStream(), ensureExists(LOCAL), StandardCopyOption.REPLACE_EXISTING);
90 | }
91 | return readLocal();
92 | } catch (Exception e) {
93 | LOGGER.warn("Could not download remote limits configurations from " + url, e);
94 | return null;
95 | }
96 | }
97 |
98 | @Nullable
99 | public FileTime getLocalLastModified() {
100 | try {
101 | if (!Files.exists(LOCAL)) {
102 | return null;
103 | }
104 | return Files.getLastModifiedTime(LOCAL);
105 | } catch (Throwable e) {
106 | return null;
107 | }
108 | }
109 |
110 | @Nullable
111 | public String readLocal() {
112 | try {
113 | return toString(Files.newInputStream(LOCAL));
114 | } catch (IOException e) {
115 | return null;
116 | }
117 | }
118 |
119 | public String readEmbedded() throws IOException {
120 | return toString(LimitsConfigurations.class.getResourceAsStream(EMBEDDED));
121 | }
122 |
123 | private String toString(InputStream in) throws IOException {
124 | if (in == null) {
125 | return null;
126 | }
127 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
128 | return reader.lines().collect(Collectors.joining());
129 | }
130 | }
131 |
132 | private boolean isValidURL(String url) {
133 | try {
134 | new URL(url);
135 | return true;
136 | } catch (MalformedURLException e) {
137 | return false;
138 | }
139 | }
140 |
141 | }
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/PluginLimits.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
14 | import com.redhat.devtools.intellij.telemetry.core.service.UserId;
15 |
16 | import java.util.List;
17 |
18 | public class PluginLimits {
19 | private final String pluginId;
20 | private final Enabled enabled;
21 | private final int refresh;
22 | private final float ratio;
23 | private final List includes;
24 | private final List excludes;
25 | private final UserId userId;
26 |
27 | PluginLimits(String pluginId, Enabled enabled, int refresh, float ratio, List includes, List excludes) {
28 | this(pluginId, enabled, refresh, ratio, includes, excludes, UserId.INSTANCE);
29 | }
30 |
31 | PluginLimits(String pluginId, Enabled enabled, int refresh, float ratio, List includes, List excludes, UserId userId) {
32 | this.pluginId = pluginId;
33 | this.enabled = enabled;
34 | this.refresh = refresh;
35 | this.ratio = ratio;
36 | this.includes = includes;
37 | this.excludes = excludes;
38 | this.userId = userId;
39 | }
40 |
41 | public String getPluginId() {
42 | return pluginId;
43 | }
44 |
45 | public boolean isDefault() {
46 | return "*".equals(pluginId);
47 | }
48 |
49 | Enabled getEnabled() {
50 | return enabled;
51 | }
52 |
53 | int getRefresh() {
54 | return refresh;
55 | }
56 |
57 | float getRatio() {
58 | return ratio;
59 | }
60 |
61 | public boolean canSend(Event event, int currentTotal) {
62 | if (event == null) {
63 | return false;
64 | }
65 | if (!isEnabled()
66 | || (isErrorOnly() && !event.hasError())) {
67 | return false;
68 | }
69 |
70 | if (!isInRatio()) {
71 | return false;
72 | }
73 |
74 | return isIncluded(event, currentTotal)
75 | && !isExcluded(event);
76 | }
77 |
78 | private boolean isInRatio() {
79 | if (userId == null) {
80 | return true;
81 | }
82 | return ratio > 0
83 | && ratio >= userId.getPercentile();
84 | }
85 |
86 | boolean isEnabled() {
87 | Enabled enabled = getEnabled();
88 | return enabled != null
89 | && enabled != Enabled.OFF;
90 | }
91 |
92 | boolean isErrorOnly() {
93 | Enabled enabled = getEnabled();
94 | return enabled == Enabled.CRASH
95 | || enabled == Enabled.ERROR;
96 | }
97 |
98 | List getIncludes() {
99 | return includes;
100 | }
101 |
102 | boolean isIncluded(Event event, int currentTotal) {
103 | Filter matching = includes.stream()
104 | .filter(filter -> filter.isMatching(event))
105 | .findAny()
106 | .orElse(null);
107 | return matching == null ||
108 | (matching.isIncludedByRatio(userId.getPercentile())
109 | && matching.isWithinDailyLimit(currentTotal));
110 | }
111 |
112 | boolean isExcluded(Event event) {
113 | Filter matching = excludes.stream()
114 | .filter(filter -> filter.isMatching(event))
115 | .findAny()
116 | .orElse(null);
117 | return matching != null
118 | && matching.isExcludedByRatio(userId.getPercentile());
119 | }
120 |
121 | List getExcludes() {
122 | return excludes;
123 | }
124 |
125 | }
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Application.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.service;
12 |
13 | import java.util.AbstractMap;
14 | import java.util.Collection;
15 | import java.util.HashMap;
16 | import java.util.Map;
17 | import java.util.Objects;
18 | import java.util.stream.Collectors;
19 |
20 | public class Application {
21 |
22 | private final String name;
23 | private final String version;
24 | private final Map properties = new HashMap<>();
25 |
26 | Application(String name, String version) {
27 | this.name = name;
28 | this.version = version;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public String getVersion() {
36 | return version;
37 | }
38 |
39 | public Application property(String key, String value) {
40 | this.properties.put(key, value);
41 | return this;
42 | }
43 |
44 | public Collection> getProperties() {
45 | return properties.entrySet().stream()
46 | .map(entry -> new AbstractMap.SimpleEntry(entry.getKey(), entry.getValue()))
47 | .collect(Collectors.toList());
48 | }
49 |
50 | @Override
51 | public boolean equals(Object o) {
52 | if (this == o) return true;
53 | if (!(o instanceof Application)) return false;
54 | Application that = (Application) o;
55 | return Objects.equals(name, that.name)
56 | && Objects.equals(version, that.version)
57 | && Objects.equals(properties, that.properties);
58 | }
59 |
60 | @Override
61 | public int hashCode() {
62 | return Objects.hash(name, version, properties);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Country.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.service;
12 |
13 | import com.fasterxml.jackson.core.type.TypeReference;
14 | import com.fasterxml.jackson.databind.ObjectMapper;
15 | import com.intellij.openapi.diagnostic.Logger;
16 | import com.redhat.devtools.intellij.telemetry.core.util.Lazy;
17 | import org.jetbrains.annotations.Nullable;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.HashMap;
22 | import java.util.Map;
23 | import java.util.TimeZone;
24 |
25 | /**
26 | * A class that provides the country for a given Timezone.
27 | * The mapping that this is based on relies on data provided the "countries-and-timezones" project
28 | * at https://github.com/manuelmhtr/countries-and-timezones
29 | */
30 | public class Country {
31 |
32 | private static final Logger LOGGER = Logger.getInstance(Country.class);
33 |
34 | private static final String TIMEZONES = "/timezones.json";
35 | private static final String KEY_COUNTRY = "c";
36 | private static final String KEY_ALTERNATIVE = "a";
37 |
38 | private static final Country INSTANCE = new Country();
39 |
40 | public static Country getInstance() {
41 | return INSTANCE;
42 | }
43 |
44 | private final Lazy
14 |
17 | 1.2.0
18 |
22 | 1.1.0
23 |
27 | 1.0.0
28 |
31 | 0.0.3
32 |
36 | 0.0.2
37 |
40 | 0.0.1
41 |
42 | - Initial release
43 |
44 | ]]>
45 |
46 |
47 | com.intellij.modules.lang
48 |
49 |
50 |
55 |
57 |
59 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/main/resources/segment-defaults.properties:
--------------------------------------------------------------------------------
1 | writeKey=qd7yNaZplhUlLFBQJoYkNHXFUm2EZ8yG
2 | debugWriteKey=ySk3bh8S8hDIGVKX9FQ1BMGOdFxbsufn
--------------------------------------------------------------------------------
/src/main/resources/telemetry-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "*": {
3 | "enabled": "all",
4 | "refresh": "12h",
5 | "includes": [
6 | {
7 | "name": "startup",
8 | "dailyLimit": 1
9 | },
10 | {
11 | "name": "*"
12 | }
13 | ],
14 | "excludes": [
15 | {
16 | "name": "shutdown",
17 | "ratio": "1.0"
18 | }
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 |
16 | import java.io.IOException;
17 | import java.nio.file.Path;
18 | import java.nio.file.Paths;
19 |
20 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
21 |
22 | class ClasspathConfigurationTest {
23 |
24 | private ClasspathConfiguration config;
25 |
26 | @BeforeEach
27 | void beforeEach() throws IOException {
28 | Path path = Paths.get("segment.properties");
29 | this.config = new ClasspathConfiguration(path);
30 | }
31 |
32 | @Test
33 | void get_loads_property_file() throws IOException {
34 | // given
35 | // when
36 | String value = config.get("writeKey");
37 | // then
38 | assertThat(value).isEqualTo("SEGPROP-normal");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.intellij.openapi.util.Pair;
14 | import org.junit.jupiter.api.BeforeEach;
15 | import org.junit.jupiter.api.Test;
16 |
17 | import java.io.IOException;
18 | import java.nio.file.Files;
19 | import java.nio.file.Path;
20 | import java.nio.file.Paths;
21 |
22 | import static com.redhat.devtools.intellij.telemetry.core.util.ConfigurationUtils.*;
23 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
24 |
25 | class FileConfigurationTest {
26 |
27 | private FileConfiguration config;
28 | private Path path;
29 |
30 | private static final Pair property1 = new Pair<>("luke", "jedy");
31 |
32 | @BeforeEach
33 | void beforeEach() throws IOException {
34 | this.path = Paths.get(System.getProperty("java.io.tmpdir"), getClass().getSimpleName() + ".properties");
35 | createPropertyFile(path, property1);
36 | this.config = new FileConfiguration(path);
37 | }
38 |
39 | @Test
40 | void get_loads_property_file() throws IOException {
41 | // given
42 | // when
43 | String value = config.get(property1.first);
44 | // then
45 | assertThat(value).isEqualTo(property1.second);
46 | }
47 |
48 | @Test
49 | void get_returns_null_if_no_file_exists() throws IOException {
50 | // given
51 | Files.delete(path);
52 | // when
53 | String value = config.get(property1.first);
54 | // then
55 | assertThat(value).isNull();
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration;
12 |
13 | import com.intellij.openapi.util.Pair;
14 | import org.junit.jupiter.api.BeforeEach;
15 | import org.junit.jupiter.api.Test;
16 |
17 | import java.io.IOException;
18 | import java.nio.file.Files;
19 | import java.nio.file.Path;
20 | import java.nio.file.Paths;
21 |
22 | import static com.redhat.devtools.intellij.telemetry.core.util.ConfigurationUtils.createPropertyFile;
23 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
24 |
25 | class SaveableFileConfigurationTest {
26 |
27 | private SaveableFileConfiguration config;
28 | private Path path;
29 |
30 | private static final Pair property1 = new Pair<>("luke", "jedy");
31 | private static final Pair property2 = new Pair<>("anakin", "sith");
32 |
33 | @BeforeEach
34 | void beforeEach() throws IOException {
35 | this.path = Paths.get(System.getProperty("java.io.tmpdir"), getClass().getSimpleName() + ".properties");
36 | createPropertyFile(path, property1);
37 | this.config = new SaveableFileConfiguration(path);
38 | }
39 |
40 | @Test
41 | void save_creates_property_file_if_it_doesnt_exist() throws IOException {
42 | // given
43 | Files.delete(path);
44 | assertThat(Files.exists(path)).isFalse();
45 | // when
46 | config.save();
47 | // then
48 | assertThat(Files.exists(path)).isTrue();
49 | }
50 |
51 | @Test
52 | void save_saves_additional_properties() throws IOException {
53 | // given
54 | assertThat(config.get(property2.first)).isNull();
55 | config.put(property2.first, property2.second);
56 | // when
57 | config.save();
58 | // then
59 | this.config = new SaveableFileConfiguration(path);
60 | assertThat(config.get(property2.first)).isEqualTo(property2.second);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/EventNameFilterTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.EventNameFilter;
14 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
15 | import com.redhat.devtools.intellij.telemetry.core.service.UserId;
16 | import org.junit.jupiter.api.Test;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 | import static org.mockito.Mockito.doReturn;
20 | import static org.mockito.Mockito.mock;
21 |
22 | public class EventNameFilterTest {
23 |
24 | @Test
25 | public void isMatching_should_match_event_name() {
26 | // given
27 | Filter filter = new EventNameFilter("yoda", 0.42f, 42);
28 | Event event = new Event(Event.Type.USER, "yoda");
29 | // when
30 | boolean matching = filter.isMatching(event);
31 | // then
32 | assertThat(matching).isTrue();
33 | }
34 |
35 | @Test
36 | public void isMatching_should_NOT_match_event_name_that_is_different() {
37 | // given
38 | Filter filter = new EventNameFilter("yoda", 0.42f, 42);
39 | Event event = new Event(Event.Type.USER, "darthvader");
40 | // when
41 | boolean matching = filter.isMatching(event);
42 | // then
43 | assertThat(matching).isFalse();
44 | }
45 |
46 | @Test
47 | public void isMatching_should_match_event_name_when_pattern_is_wildcard() {
48 | // given
49 | Filter filter = new EventNameFilter("*", 0.42f, 42);
50 | Event event = new Event(Event.Type.USER, "skywalker");
51 | // when
52 | boolean matching = filter.isMatching(event);
53 | // then
54 | assertThat(matching).isTrue();
55 | }
56 |
57 | @Test
58 | public void isMatching_should_match_event_name_when_pattern_has_name_with_wildcards() {
59 | // given
60 | Filter filter = new EventNameFilter("*walk*", 0.42f, 42);
61 | Event event = new Event(Event.Type.USER, "skywalker");
62 | // when
63 | boolean matching = filter.isMatching(event);
64 | // then
65 | assertThat(matching).isTrue();
66 | }
67 |
68 | @Test
69 | public void isIncludedByRatio_returns_true_if_percentile_is_within_ratio() {
70 | // given
71 | Filter filter = new EventNameFilter("ignore", 0.1f, EventNameFilter.DAILY_LIMIT_UNSPECIFIED);
72 | // when
73 | boolean isWithin = filter.isIncludedByRatio(0.1f);
74 | // then
75 | assertThat(isWithin).isTrue();
76 | }
77 |
78 | @Test
79 | public void isIncludedByRatio_returns_false_if_percentile_is_NOT_within_ratio() {
80 | // given
81 | Filter filter = new EventNameFilter("ignore", 0.1f, EventNameFilter.DAILY_LIMIT_UNSPECIFIED);
82 | // when
83 | boolean isWithin = filter.isIncludedByRatio(0.2f);
84 | // then
85 | assertThat(isWithin).isFalse();
86 | }
87 |
88 | @Test
89 | public void isIncludedByRatio_returns_false_if_ratio_is_0() {
90 | // given
91 | Filter filter = new EventNameFilter("ignore", 0, EventNameFilter.DAILY_LIMIT_UNSPECIFIED);
92 | // when
93 | boolean isWithin = filter.isIncludedByRatio(0);
94 | // then
95 | assertThat(isWithin).isFalse();
96 | }
97 |
98 | @Test
99 | public void isWithinDailyLimit_returns_true_if_dailyLyimit_is_unspecified() {
100 | // given
101 | Filter filter = new EventNameFilter("ignore", -1, EventNameFilter.DAILY_LIMIT_UNSPECIFIED);
102 | // when
103 | boolean isWithin = filter.isWithinDailyLimit(Integer.MAX_VALUE);
104 | // then
105 | assertThat(isWithin).isTrue();
106 | }
107 |
108 | @Test
109 | public void isWithinDailyLimit_returns_true_if_dailyLimit_is_NOT_reached() {
110 | // given
111 | Filter filter = new EventNameFilter("ignore", -1, Integer.MAX_VALUE);
112 | // when
113 | boolean isWithin = filter.isWithinDailyLimit(0);
114 | // then
115 | assertThat(isWithin).isTrue();
116 | }
117 |
118 | @Test
119 | public void isWithinDailyLimit_returns_false_if_dailyLimit_is_reached() {
120 | // given
121 | Filter filter = new EventNameFilter("ignore", -1, 1);
122 | // when
123 | boolean isWithin = filter.isWithinDailyLimit(2);
124 | // then
125 | assertThat(isWithin).isFalse();
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/EventPropertyFilterTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.EventNameFilter;
14 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
15 | import org.junit.jupiter.api.Test;
16 |
17 | import java.util.Map;
18 |
19 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.*;
20 | import static org.assertj.core.api.Assertions.assertThat;
21 |
22 | public class EventPropertyFilterTest {
23 |
24 | @Test
25 | public void isMatching_should_match_any_event_with_exact_property_name_and_value() {
26 | // given
27 | Filter filter = new EventPropertyFilter("yoda", "jedi");
28 | Event event = new Event(Event.Type.USER, "there are jedis in the rebellion", Map.of(
29 | "yoda", "jedi"));
30 | // when
31 | boolean matching = filter.isMatching(event);
32 | // then
33 | assertThat(matching).isTrue();
34 | }
35 |
36 | @Test
37 | public void isMatching_should_match_any_event_with_exact_property_name_and_wildcard_value() {
38 | // given
39 | Filter filter = new EventPropertyFilter("yoda", "*jedi*");
40 | Event event = new Event(Event.Type.USER, "there are jedis on both sides", Map.of(
41 | "yoda", "is a master jedi!"));
42 | // when
43 | boolean matching = filter.isMatching(event);
44 | // then
45 | assertThat(matching).isTrue();
46 | }
47 |
48 | @Test
49 | public void isMatching_should_NOT_match_event_that_doesnt_have_given_property_name() {
50 | // given
51 | Filter filter = new EventPropertyFilter("yoda", "*jedi*");
52 | Event event = new Event(Event.Type.USER, "there are jedis on both sides", Map.of(
53 | "darth vader", "is a master jedi!")); // key doesnt match
54 | // when
55 | boolean matching = filter.isMatching(event);
56 | // then
57 | assertThat(matching).isFalse();
58 | }
59 |
60 | @Test
61 | public void isMatching_should_NOT_match_event_that_has_given_property_name_but_doesnt_have_property_value() {
62 | // given
63 | Filter filter = new EventPropertyFilter("yoda", "*jedi*");
64 | Event event = new Event(Event.Type.USER, "there are jedis on both sides", Map.of(
65 | "yoda", "is stronger than the emperor")); // value doesnt match
66 | // when
67 | boolean matching = filter.isMatching(event);
68 | // then
69 | assertThat(matching).isFalse();
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/Mocks.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
14 | import com.redhat.devtools.intellij.telemetry.core.service.UserId;
15 |
16 | import java.util.HashMap;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Filter.*;
21 | import static org.mockito.Mockito.doReturn;
22 | import static org.mockito.Mockito.mock;
23 |
24 | public class Mocks {
25 |
26 | public static Event event() {
27 | return event(new HashMap<>());
28 | }
29 |
30 | public static Event event(Map properties) {
31 | return new Event(null, null, properties);
32 | }
33 |
34 | public static UserId userId(float percentile) {
35 | UserId userId = mock(UserId.class);
36 | doReturn(percentile)
37 | .when(userId).getPercentile();
38 | return userId;
39 | }
40 |
41 | public static PluginLimits pluginLimitsWithIncludesExcludes(List includes, List excludes) {
42 | return pluginLimitsWithIncludesExcludesWithPercentile(
43 | includes,
44 | excludes,
45 | userId(0));
46 | }
47 |
48 | public static PluginLimits pluginLimitsWithIncludesExcludesWithPercentile(List includes, List excludes, UserId userId) {
49 | return new PluginLimits(
50 | "jedis",
51 | Enabled.ALL, // ignore
52 | -1, // ignore
53 | 1f, // ratio 100%
54 | includes,
55 | excludes,
56 | userId);
57 | }
58 |
59 | public static EventNameFilter eventNameFilter(final boolean isMatching, boolean isIncludedRatio, boolean isExcludedRatio) {
60 | return eventNameFilter(isMatching, isIncludedRatio, isExcludedRatio, Integer.MAX_VALUE); // no daily limit
61 | }
62 |
63 | public static EventNameFilter eventNameFilter(final boolean isMatching, boolean isIncludedRatio, boolean isExcludedRatio, int dailyLimit) {
64 | return new EventNameFilter( null, -1f, dailyLimit) {
65 | @Override
66 | public boolean isMatching(Event event) {
67 | return isMatching;
68 | }
69 |
70 | @Override
71 | public boolean isExcludedByRatio(float percentile) {
72 | return isExcludedRatio;
73 | }
74 |
75 | @Override
76 | public boolean isIncludedByRatio(float percentile) {
77 | return isIncludedRatio;
78 | }
79 | };
80 | }
81 |
82 | public static EventNameFilter eventNameFilterFakeWithRatio(float ratio) {
83 | return new EventNameFilter( null, ratio, Integer.MAX_VALUE) {
84 | @Override
85 | public boolean isMatching(Event event) {
86 | return true;
87 | }
88 | };
89 | }
90 |
91 | public static EventNameFilter eventNameWithDailyLimit(int dailyLimit) {
92 | return new EventNameFilter( null, 1, dailyLimit) {
93 | @Override
94 | public boolean isMatching(Event event) {
95 | return true;
96 | }
97 | };
98 | }
99 |
100 | public static EventPropertyFilter eventProperty() {
101 | return new EventPropertyFilter( null, null) {
102 | @Override
103 | public boolean isMatching(Event event) {
104 | return true;
105 | }
106 | };
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/limits/PluginLimitsRatioTests.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.configuration.limits;
12 |
13 | import com.redhat.devtools.intellij.telemetry.core.service.Event;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.Arguments;
16 | import org.junit.jupiter.params.provider.MethodSource;
17 |
18 | import java.util.Collections;
19 | import java.util.List;
20 | import java.util.stream.Stream;
21 |
22 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Mocks.event;
23 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Mocks.eventNameFilterFakeWithRatio;
24 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Mocks.pluginLimitsWithIncludesExcludesWithPercentile;
25 | import static com.redhat.devtools.intellij.telemetry.core.configuration.limits.Mocks.userId;
26 | import static org.assertj.core.api.Assertions.assertThat;
27 |
28 | public class PluginLimitsRatioTests {
29 |
30 | @ParameterizedTest
31 | @MethodSource("canSend_for_include_ratio_and_percentile")
32 | public void canSend_for_given_ratio_in_limits_and_user_percentile(float limitsRatio, float percentile, boolean shouldSend) {
33 | // given
34 | PluginLimits limits = new PluginLimits(
35 | "jedis",
36 | Enabled.ALL, // ignore
37 | -1, // ignore
38 | limitsRatio,
39 | Collections.emptyList(),
40 | Collections.emptyList(),
41 | userId(percentile));
42 | Event event = event();
43 | // when
44 | boolean canSend = limits.canSend(event, 0);
45 | // then
46 | assertThat(canSend).isEqualTo(shouldSend);
47 | }
48 |
49 | @ParameterizedTest
50 | @MethodSource("canSend_for_include_ratio_and_percentile")
51 | public void canSend_for_given_ratio_in_include_filter_and_user_percentile(float filterRatio, float percentile, boolean shouldSend) {
52 | // given
53 | PluginLimits limits = new PluginLimits(
54 | "jedis",
55 | Enabled.ALL, // ignore
56 | -1, // ignore
57 | 1f, // ratio 100%
58 | List.of(
59 | eventNameFilterFakeWithRatio(filterRatio)
60 | ),
61 | Collections.emptyList(),
62 | userId(percentile));
63 | Event event = event();
64 | // when
65 | boolean canSend = limits.canSend(event,0);
66 | // then
67 | assertThat(canSend).isEqualTo(shouldSend);
68 | }
69 |
70 | private static Stream canSend_for_include_ratio_and_percentile() {
71 | return Stream.of(
72 | Arguments.of(0f, 0f, false), // ratio: 0, percentile: .2 -> false
73 | Arguments.of(0f, .2f, false), // ratio: 0, percentile: .2 -> false
74 | Arguments.of(.1f, .1f, true), // ratio: .1, percentile: .1 -> true
75 | Arguments.of(.5f, .4f, true), // ratio: .5, percentile: .4 -> true
76 | Arguments.of(.5f, .5f, true), // ratio: .5, percentile: .5 -> true
77 | Arguments.of(.5f, .6f, false), // ratio: .5, percentile: .6 -> false
78 | Arguments.of(1f, .6f, true), // ratio: 1, percentile: .6 -> true
79 | Arguments.of(1f, 1f, true) // ratio: 1, percentile: 1 -> true
80 | );
81 | }
82 |
83 | @ParameterizedTest
84 | @MethodSource("canSend_for_exclude_ratio_and_percentile")
85 | public void canSend_for_given_ratio_in_exclude_filter_and_user_percentile(float filterRatio, float percentile, boolean shouldSend) {
86 | // given
87 | PluginLimits limits = pluginLimitsWithIncludesExcludesWithPercentile(
88 | Collections.emptyList(),
89 | List.of(
90 | eventNameFilterFakeWithRatio(filterRatio)
91 | ),
92 | userId(percentile));
93 | Event event = event();
94 | // when
95 | boolean canSend = limits.canSend(event,0);
96 | // then
97 | assertThat(canSend).isEqualTo(shouldSend);
98 | }
99 |
100 | private static Stream canSend_for_exclude_ratio_and_percentile() {
101 | return Stream.of(
102 | Arguments.of(0f, 0f, true), // exclude ratio: 0, percentile: .2 -> true
103 | Arguments.of(0f, .2f, true), // exclude ratio: 0, percentile: .2 -> true
104 | Arguments.of(.1f, .1f, true), // exclude ratio: .1, percentile: .1 -> true
105 | Arguments.of(.5f, .4f, true), // exclude ratio: .5, percentile: .4 -> true
106 | Arguments.of(.5f, .5f, true), // exclude ratio: .5, percentile: .5 -> true
107 | Arguments.of(.5f, .6f, false), // exclude ratio: .5, percentile: .6 -> false
108 | Arguments.of(1f, .6f, false), // exclude ratio: 1, percentile: .6 -> false
109 | Arguments.of(1f, 1f, false) // exclude ratio: 1, percentile: 1 -> false
110 | );
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/CountryTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.service;
12 |
13 | import org.junit.jupiter.api.Test;
14 |
15 | import java.util.TimeZone;
16 |
17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
18 |
19 | class CountryTest {
20 |
21 | @Test
22 | void get_should_return_country_for_timezoneId() {
23 | // given
24 | // when
25 | String country = Country.getInstance().get(TimeZone.getTimeZone("Europe/Zurich"));
26 | // then
27 | assertThat(country).isEqualTo("CH");
28 | }
29 |
30 | @Test
31 | void get_should_return_null_for_null_timezoneId() {
32 | // given
33 | // when
34 | String country = Country.getInstance().get((String) null);
35 | // then
36 | assertThat(country).isEqualTo(null);
37 | }
38 |
39 | @Test
40 | void get_should_return_timezoneId_for_unknown_timezoneId() {
41 | // given
42 | // when
43 | TimeZone timeZone = TimeZone.getDefault();
44 | timeZone.setID("Aldreean/Organa Major");
45 | String country = Country.getInstance().get(timeZone);
46 | // then
47 | assertThat(country).isNull();
48 | }
49 |
50 | @Test
51 | void get_should_return_country_for_timezoneId_with_alternative() {
52 | // given
53 | // when "America/Argentina/ComodRivadavia" -> "a: America/Argentina/Catamarca" -> "c: AR" -> "Argentina"
54 | String country = Country.getInstance().get(TimeZone.getTimeZone("America/Argentina/ComodRivadavia"));
55 | // then
56 | assertThat(country).isEqualTo("AR");
57 | }
58 |
59 | @Test
60 | void get_should_return_null_for_timezoneId_without_country_nor_alternative() {
61 | // given
62 | // when
63 | String country = Country.getInstance().get(TimeZone.getTimeZone("CET"));
64 | // then
65 | assertThat(country).isNull();
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/Fakes.java:
--------------------------------------------------------------------------------
1 | package com.redhat.devtools.intellij.telemetry.core.service;
2 |
3 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration;
4 | import com.redhat.devtools.intellij.telemetry.core.service.segment.ISegmentConfiguration;
5 | import org.mockito.stubbing.Answer;
6 |
7 | import static org.mockito.ArgumentMatchers.anyBoolean;
8 | import static org.mockito.Mockito.doReturn;
9 | import static org.mockito.Mockito.mock;
10 | import static org.mockito.Mockito.when;
11 |
12 | public class Fakes {
13 |
14 | public static TelemetryConfiguration telemetryConfiguration(boolean enabled, boolean configured) {
15 | TelemetryConfiguration configuration = mock(TelemetryConfiguration.class);
16 | doReturn(enabled)
17 | .when(configuration).isEnabled();
18 | doReturn(configured)
19 | .when(configuration).isConfigured();
20 | return configuration;
21 | }
22 |
23 | public static Environment environment(
24 | String extensionName,
25 | String extensionVersion,
26 | String applicationName,
27 | String applicationVersion,
28 | String platform_name,
29 | String platform_distribution,
30 | String platform_version,
31 | String locale,
32 | String timezone,
33 | String country) {
34 | return new Environment.Builder()
35 | .ide(new IDE(applicationName, applicationVersion))
36 | .locale(locale)
37 | .timezone(timezone)
38 | .country(country)
39 | .platform(new Platform(platform_name, platform_distribution, platform_version))
40 | .plugin(new Plugin.Factory().create(extensionName, extensionVersion, "bogusId"))
41 | .build();
42 | }
43 |
44 | public static ISegmentConfiguration segmentConfiguration(String normalWriteKey, String debugWriteKey) {
45 | ISegmentConfiguration configuration = mock(ISegmentConfiguration.class);
46 | when(configuration.getNormalWriteKey())
47 | .thenReturn(normalWriteKey);
48 | when(configuration.getDebugWriteKey())
49 | .thenReturn(debugWriteKey);
50 | when(configuration.getWriteKey(anyBoolean()))
51 | .thenAnswer((Answer) invocation -> {
52 | Boolean isDebug = invocation.getArgument(0);
53 | if (isDebug) {
54 | return debugWriteKey;
55 | } else {
56 | return normalWriteKey;
57 | }
58 | });
59 | return configuration;
60 | }
61 |
62 | public static TelemetryConfiguration telemetryConfiguration(TelemetryConfiguration.Mode mode) {
63 | TelemetryConfiguration configuration = mock(TelemetryConfiguration.class);
64 | when(configuration.isEnabled())
65 | .thenReturn(mode != TelemetryConfiguration.Mode.DISABLED);
66 | when(configuration.isConfigured())
67 | .thenReturn(true);
68 | when(configuration.getMode())
69 | .thenReturn(mode);
70 | return configuration;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/IdentifyTraitsPersistenceTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2022 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.service.segment;
12 |
13 | import com.google.gson.Gson;
14 | import org.junit.jupiter.api.BeforeEach;
15 | import org.junit.jupiter.api.Test;
16 |
17 | import java.io.IOException;
18 | import java.nio.file.Path;
19 | import java.util.stream.Stream;
20 |
21 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
22 | import static org.mockito.ArgumentMatchers.any;
23 | import static org.mockito.Mockito.doThrow;
24 | import static org.mockito.Mockito.never;
25 | import static org.mockito.Mockito.spy;
26 | import static org.mockito.Mockito.times;
27 | import static org.mockito.Mockito.verify;
28 |
29 | class IdentifyTraitsPersistenceTest {
30 |
31 | private IdentifyTraits identifyTraits;
32 |
33 | private TestableIdentifyTraitsPersistence persistence;
34 |
35 | @BeforeEach
36 | void beforeEach() {
37 | this.identifyTraits = createIdentifyTraits();
38 | this.persistence = spy(new TestableIdentifyTraitsPersistence(new Gson().toJson(identifyTraits)));
39 | }
40 |
41 | @Test
42 | void get_should_return_stored_traits() {
43 | // given
44 | // when
45 | IdentifyTraits stored = persistence.get();
46 | // then
47 | assertThat(stored).isEqualTo(identifyTraits);
48 | }
49 |
50 |
51 | @Test
52 | void get_should_return_null_if_file_cannot_be_loaded() throws IOException {
53 | // given
54 | doThrow(IOException.class)
55 | .when(persistence).getLines(any());
56 | // when
57 | IdentifyTraits stored = persistence.get();
58 | // then
59 | assertThat(stored).isNull();
60 | }
61 |
62 | @Test
63 | void get_should_load_file_only_once() throws IOException {
64 | // given
65 | // when
66 | persistence.get();
67 | persistence.get();
68 | // then
69 | verify(persistence, times(1)).getLines(any());
70 | }
71 |
72 | @Test
73 | void set_should_NOT_write_to_file_if_traits_are_equal() throws IOException {
74 | // given
75 | IdentifyTraits identifyTraits = createIdentifyTraits();
76 | persistence.get(); // initialize stored traits
77 | // when
78 | persistence.set(identifyTraits);
79 | // then
80 | verify(persistence, never()).writeFile(any(), any());
81 | }
82 |
83 | @Test
84 | void set_should_write_to_file() throws IOException {
85 | // given
86 | IdentifyTraits identifyTraits = new IdentifyTraits("en-US", "GMT+2:00", "Linux", "42", "Fedora");
87 | // when
88 | persistence.set(identifyTraits);
89 | // then
90 | verify(persistence).writeFile(any(), any());
91 | }
92 |
93 | private IdentifyTraits createIdentifyTraits() {
94 | return new IdentifyTraits(
95 | "ewokese-Endor locale",
96 | "AMC+2:00",
97 | "AlderaanOS",
98 | "v42",
99 | "blue green planet distribution");
100 | }
101 |
102 | private static final class TestableIdentifyTraitsPersistence extends IdentifyTraitsPersistence {
103 |
104 | private final String fileContent;
105 |
106 | private TestableIdentifyTraitsPersistence(String fileContent) {
107 | this.fileContent = fileContent;
108 | }
109 |
110 | @Override
111 | public Stream getLines(Path file) throws IOException {
112 | return Stream.of(fileContent);
113 | }
114 |
115 | @Override
116 | public void createFileAndParent(Path file) throws IOException {
117 | super.createFileAndParent(file);
118 | }
119 |
120 | @Override
121 | public void writeFile(String event, Path file) throws IOException {
122 | super.writeFile(event, file);
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.service.segment;
12 |
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 |
16 | import java.io.IOException;
17 |
18 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
19 |
20 | class SegmentConfigurationTest {
21 |
22 | private SegmentConfiguration config;
23 |
24 | @BeforeEach
25 | void beforeEach() {
26 | this.config = new SegmentConfiguration(getClass().getClassLoader());
27 | }
28 |
29 | @Test
30 | void getNormalKey_should_return_value_in_classpath_file() throws IOException {
31 | // given
32 | // when
33 | String writeKey = config.getNormalWriteKey();
34 | // then
35 | assertThat(writeKey).isEqualTo("SEGPROP-normal");
36 | }
37 |
38 | @Test
39 | void getDebugKey_should_return_value_in_classpath_file() throws IOException {
40 | // given
41 | // when
42 | String writeKey = config.getDebugWriteKey();
43 | // then
44 | assertThat(writeKey).isEqualTo("SEGPROP-debug");
45 | }
46 |
47 | @Test
48 | void getNormalKey_should_return_overriding_system_prop() throws IOException {
49 | // given
50 | String syspropWriteKey = "SYSTEMPROP-normal";
51 | System.setProperty("writeKey", syspropWriteKey);
52 | // when
53 | String writeKey = config.getNormalWriteKey();
54 | // then
55 | assertThat(writeKey).isEqualTo(syspropWriteKey);
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtilsTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2021 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.util;
12 |
13 | import org.junit.jupiter.api.Test;
14 | import static org.assertj.core.api.Assertions.assertThat;
15 |
16 | public class AnonymizeUtilsTest {
17 |
18 | @Test
19 | void anonymizeEmail_should_replace_email() {
20 | // given
21 | String email = "adietish@redhat.com";
22 | String msgWithEmail = "This is the email address " + email + " within a message.";
23 | // when
24 | String anonymized = AnonymizeUtils.anonymizeEmail(msgWithEmail);
25 | // then
26 | assertThat(anonymized).doesNotContain(email);
27 | assertThat(anonymized).contains(AnonymizeUtils.ANONYMOUS_EMAIL);
28 | }
29 |
30 | @Test
31 | void anonymizeEmail_should_NOT_replace_bogus_email() {
32 | // given
33 | String bogusEmail = "adietish@redhat";
34 | String msgWithBogusEmail = "This is the email address " + bogusEmail + " within a message.";
35 | // when
36 | String anonymized = AnonymizeUtils.anonymizeEmail(msgWithBogusEmail);
37 | // then
38 | assertThat(anonymized).contains(bogusEmail);
39 | assertThat(anonymized).doesNotContain(AnonymizeUtils.ANONYMOUS_EMAIL);
40 | }
41 |
42 | @Test
43 | void anonymizeUserName_should_replace_username() {
44 | // given
45 | String username = AnonymizeUtils.USER_NAME;
46 | String msgWithUsername = "This is the username " + username + " within a message.";
47 | // when
48 | String anonymized = AnonymizeUtils.anonymizeUserName(msgWithUsername);
49 | // then
50 | assertThat(anonymized).doesNotContain(username);
51 | assertThat(anonymized).contains(AnonymizeUtils.ANONYMOUS_USER_NAME);
52 | }
53 |
54 | @Test
55 | void anonymizeHomeDir_should_replace_homedir() {
56 | // given
57 | String homeDir = AnonymizeUtils.HOME_DIR;
58 | String msgWithHomeDir = "This is the path to the " + homeDir + " within a message.";
59 | // when
60 | String anonymized = AnonymizeUtils.anonymizeHomeDir(msgWithHomeDir);
61 | // then
62 | assertThat(anonymized).doesNotContain(homeDir);
63 | assertThat(anonymized).contains(AnonymizeUtils.ANONYMOUS_HOMEDIR);
64 | }
65 |
66 | @Test
67 | void anonymizeHomeDir_should_NOT_replace_other_directory() {
68 | // given
69 | String otherDir = "C:\\\\Documents and Settings\\\\All Users";
70 | String msgWithOtherDir = "This is the path to the " + otherDir + " within a message.";
71 | // when
72 | String anonymized = AnonymizeUtils.anonymizeHomeDir(msgWithOtherDir);
73 | // then
74 | assertThat(anonymized).contains(otherDir);
75 | assertThat(anonymized).doesNotContain(AnonymizeUtils.ANONYMOUS_HOMEDIR);
76 | }
77 |
78 | @Test
79 | void anonymizeTmpDir_should_replace_tmpDir() {
80 | // given
81 | String tmpDir = AnonymizeUtils.TMP_DIR;
82 | String msgWithTmpDir = "This is the path to the " + tmpDir + " within a message.";
83 | // when
84 | String anonymized = AnonymizeUtils.anonymizeTmpDir(msgWithTmpDir);
85 | // then
86 | assertThat(anonymized).doesNotContain(tmpDir);
87 | assertThat(anonymized).contains(AnonymizeUtils.ANONYMOUS_TMPDIR);
88 | }
89 |
90 | @Test
91 | void replaceIP_should_replace_IP() {
92 | // given
93 | String ip = "192.168.0.1";
94 | String msgWithIp = "This is the ip " + ip + " within a message.";
95 | // when
96 | String anonymized = AnonymizeUtils.anonymizeIP(msgWithIp);
97 | // then
98 | assertThat(anonymized).doesNotContain(ip);
99 | assertThat(anonymized).contains(AnonymizeUtils.ANONYMOUS_IP);
100 | }
101 |
102 | @Test
103 | void replaceIP_should_NOT_replace_bogus_IP() {
104 | // given
105 | String bogusIP = "10.0.12";
106 | String msgWithIp = "This is the ip " + bogusIP + " within a message.";
107 | // when
108 | String anonymized = AnonymizeUtils.anonymizeIP(msgWithIp);
109 | // then
110 | assertThat(anonymized).contains(bogusIP);
111 | assertThat(anonymized).doesNotContain(AnonymizeUtils.ANONYMOUS_IP);
112 | }
113 |
114 | @Test
115 | void anonymizeResource_should_anonymize_resource_and_namespace() {
116 | // given
117 | String resource = "smurf village";
118 | String namespace = "blue county";
119 | String withResourceAndNamespace = resource + " is located in " + namespace;
120 | assertThat(withResourceAndNamespace).contains(resource).contains(namespace);
121 | // when
122 | String anonymized = AnonymizeUtils.anonymizeResource(resource, namespace, withResourceAndNamespace);
123 | // then
124 | assertThat(anonymized)
125 | .doesNotContain(resource)
126 | .doesNotContain(namespace);
127 | assertThat(anonymized)
128 | .contains(AnonymizeUtils.ANONYMOUS_RESOURCENAME)
129 | .contains(AnonymizeUtils.ANONYMOUS_NAMESPACE);
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/BasicGlobPatternTest.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * Copyright (c) 2024 Red Hat, Inc.
3 | * Distributed under license by Red Hat, Inc. All rights reserved.
4 | * This program is made available under the terms of the
5 | * Eclipse Public License v2.0 which accompanies this distribution,
6 | * and is available at http://www.eclipse.org/legal/epl-v20.html
7 | *
8 | * Contributors:
9 | * Red Hat, Inc. - initial API and implementation
10 | ******************************************************************************/
11 | package com.redhat.devtools.intellij.telemetry.core.util;
12 |
13 | import org.junit.jupiter.api.Test;
14 |
15 | import java.util.regex.PatternSyntaxException;
16 |
17 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
18 | import static org.junit.jupiter.api.Assertions.assertThrows;
19 |
20 | public class BasicGlobPatternTest {
21 |
22 | @Test
23 | public void compile_should_throw_when_range_is_invalid() {
24 | // given, when, then
25 | assertThrows(PatternSyntaxException.class, () -> BasicGlobPattern.compile("[5-1] jedi"));
26 | }
27 |
28 | @Test
29 | public void compile_should_throw_when_brace_expansions_are_nested() {
30 | // given, when, then
31 | assertThrows(PatternSyntaxException.class, () -> BasicGlobPattern.compile("{{yoda,obiwan}"));
32 | }
33 |
34 | @Test
35 | public void compile_should_throw_when_brace_expansions_are_not_closed() {
36 | // given, when, then
37 | assertThrows(PatternSyntaxException.class, () -> BasicGlobPattern.compile("{yoda,obiwan"));
38 | }
39 |
40 | @Test
41 | public void machtes_should_match_expression_that_starts_and_ends_with_wildcard() {
42 | // given
43 | BasicGlobPattern glob = BasicGlobPattern.compile("*yoda*");
44 | // when, then
45 | assertThat(glob.matches("master yoda is a jedi master")).isTrue();
46 | assertThat(glob.matches("yoda")).isTrue(); // * matches no character, too
47 | assertThat(glob.matches("master yoda")).isTrue(); // * matches no character, too
48 | assertThat(glob.matches("master obiwan is a jedi master, too")).isFalse();
49 | }
50 |
51 | @Test
52 | public void machtes_should_match_expression_that_starts_with_wildcard() {
53 | // given
54 | BasicGlobPattern glob = BasicGlobPattern.compile("*yoda");
55 | // when, then
56 | assertThat(glob.matches("master yoda")).isTrue();
57 | assertThat(glob.matches("yoda")).isTrue();
58 | assertThat(glob.matches("master obiwan")).isFalse();
59 | }
60 |
61 | @Test
62 | public void machtes_should_match_expression_that_has_a_wildcard() {
63 | // given
64 | BasicGlobPattern glob = BasicGlobPattern.compile("y*da");
65 | // when, then
66 | assertThat(glob.matches("yoda")).isTrue();
67 | assertThat(glob.matches("yooooda")).isTrue();
68 | }
69 |
70 | @Test
71 | public void machtes_should_match_expression_that_starts_and_ends_with_placeholder() {
72 | // given
73 | BasicGlobPattern glob = BasicGlobPattern.compile("?this is yoda?");
74 | // when, then
75 | assertThat(glob.matches("!this is yoda!")).isTrue();
76 | assertThat(glob.matches("!!this is yoda!")).isFalse();
77 | assertThat(glob.matches("this is yoda!")).isFalse();
78 | }
79 |
80 | @Test
81 | public void machtes_should_match_expression_that_has_placeholders() {
82 | // given
83 | BasicGlobPattern glob = BasicGlobPattern.compile("y??a");
84 | // when, then
85 | assertThat(glob.matches("yoda")).isTrue();
86 | assertThat(glob.matches("yiza")).isTrue();
87 | assertThat(glob.matches("yoooda")).isFalse();
88 | }
89 |
90 | @Test
91 | public void machtes_should_match_expression_with_brace_expansions() {
92 | // given
93 | BasicGlobPattern glob = BasicGlobPattern.compile("{yoda,obiwan,skywalker} is a jedi");
94 | // when, then
95 | assertThat(glob.matches("yoda is a jedi")).isTrue();
96 | assertThat(glob.matches("obiwan is a jedi")).isTrue();
97 | assertThat(glob.matches("skywalker is a jedi")).isTrue();
98 | assertThat(glob.matches("darthvader is a jedi")).isFalse();
99 | }
100 |
101 | @Test
102 | public void machtes_should_match_empty_brace_expansion() {
103 | // given
104 | BasicGlobPattern glob = BasicGlobPattern.compile("{yoda,darth,} the jedi");
105 | // when, then
106 | assertThat(glob.matches("yoda the jedi")).isTrue();
107 | assertThat(glob.matches(" the jedi")).isTrue(); // empty alternative
108 | }
109 |
110 | @Test
111 | public void machtes_should_match_expression_with_a_range() {
112 | // given
113 | BasicGlobPattern glob = BasicGlobPattern.compile("jedi [0-4]");
114 | // when, then
115 | assertThat(glob.matches("jedi 0")).isTrue();
116 | assertThat(glob.matches("jedi 1")).isTrue();
117 | assertThat(glob.matches("jedi 4")).isTrue();
118 | assertThat(glob.matches("jedi 5")).isFalse();
119 | }
120 |
121 | @Test
122 | public void machtes_should_match_expression_with_alternatives() {
123 | // given
124 | BasicGlobPattern glob = BasicGlobPattern.compile("jedi [abc]");
125 | // when, then
126 | assertThat(glob.matches("jedi a")).isTrue();
127 | assertThat(glob.matches("jedi b")).isTrue();
128 | assertThat(glob.matches("jedi c")).isTrue();
129 | assertThat(glob.matches("jedi d")).isFalse();
130 | }
131 |
132 | @Test
133 | public void machtes_should_match_parenthesis_and_pipe_as_normal_characters() {
134 | // given
135 | BasicGlobPattern glob = BasicGlobPattern.compile("jedi(s|42)");
136 | // when, then
137 | assertThat(glob.matches("jedi(s|42)")).isTrue();
138 | assertThat(glob.matches("jedi(s)")).isFalse();
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/ConfigurationUtils.java:
--------------------------------------------------------------------------------
1 | package com.redhat.devtools.intellij.telemetry.core.util;
2 |
3 | import com.intellij.openapi.util.Pair;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.OutputStream;
8 | import java.nio.charset.StandardCharsets;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 | import java.util.Arrays;
12 | import java.util.stream.Collectors;
13 |
14 | public class ConfigurationUtils {
15 |
16 | public static void createPropertyFile(Path path, Pair... keyValues) throws IOException {
17 | File file = path.toFile();
18 | file.createNewFile();
19 | file.deleteOnExit();
20 | String content = Arrays.stream(keyValues)
21 | .map(keyValue -> keyValue.first + "=" + keyValue.second)
22 | .collect(Collectors.joining("\n"));
23 | try (OutputStream stream = Files.newOutputStream(path)) {
24 | stream.write(content.getBytes(StandardCharsets.UTF_8));
25 | }
26 | }
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/util/BlockingFlush.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2014 Segment, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package com.redhat.devtools.intellij.telemetry.util;
25 |
26 | import com.segment.analytics.Analytics;
27 | import com.segment.analytics.Callback;
28 | import com.segment.analytics.MessageTransformer;
29 | import com.segment.analytics.Plugin;
30 | import com.segment.analytics.messages.Message;
31 | import com.segment.analytics.messages.MessageBuilder;
32 |
33 | import java.util.concurrent.Phaser;
34 |
35 | /**
36 | * The {@link Analytics} class doesn't come with a blocking {@link Analytics#flush()} implementation
37 | * out of the box. It's trivial to build one using a {@link Phaser} that monitors requests and is
38 | * able to block until they're uploaded.
39 | *
40 | *
41 | * BlockingFlush blockingFlush = BlockingFlush.create();
42 | * Analytics analytics = Analytics.builder(writeKey)
43 | * .plugin(blockingFlush.plugin())
44 | * .build();
45 | *
46 | * // Do some work.
47 | *
48 | * analytics.flush(); // Trigger a flush.
49 | * blockingFlush.block(); // Block until the flush completes.
50 | * analytics.shutdown(); // Shut down after the flush is complete.
51 | *
52 | */
53 | public class BlockingFlush {
54 |
55 | public static BlockingFlush create() {
56 | return new BlockingFlush();
57 | }
58 |
59 | BlockingFlush() {
60 | this.phaser = new Phaser(1);
61 | }
62 |
63 | final Phaser phaser;
64 |
65 | public Plugin plugin() {
66 | return new Plugin() {
67 | @Override
68 | public void configure(Analytics.Builder builder) {
69 | builder.messageTransformer(
70 | new MessageTransformer() {
71 | @Override
72 | public boolean transform(MessageBuilder builder) {
73 | phaser.register();
74 | return true;
75 | }
76 | });
77 |
78 | builder.callback(
79 | new Callback() {
80 | @Override
81 | public void success(Message message) {
82 | phaser.arrive();
83 | }
84 |
85 | @Override
86 | public void failure(Message message, Throwable throwable) {
87 | phaser.arrive();
88 | }
89 | });
90 | }
91 | };
92 | }
93 |
94 | public void block() {
95 | phaser.arriveAndAwaitAdvance();
96 | }
97 | }
--------------------------------------------------------------------------------
/src/test/java/com/redhat/devtools/intellij/telemetry/util/StdOutLogging.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2014 Segment, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package com.redhat.devtools.intellij.telemetry.util;
25 |
26 | import com.segment.analytics.Analytics;
27 | import com.segment.analytics.Callback;
28 | import com.segment.analytics.Log;
29 | import com.segment.analytics.Plugin;
30 | import com.segment.analytics.messages.Message;
31 |
32 | /**
33 | * A {@link Plugin} implementation that redirects client logs to standard output and logs callback
34 | * events.
35 | */
36 | public class StdOutLogging implements Plugin {
37 | @Override
38 | public void configure(Analytics.Builder builder) {
39 | builder.log(
40 | new Log() {
41 | @Override
42 | public void print(Level level, String format, Object... args) {
43 | System.out.println(level + ":\t" + String.format(format, args));
44 | }
45 |
46 | @Override
47 | public void print(Level level, Throwable error, String format, Object... args) {
48 | System.out.println(level + ":\t" + String.format(format, args));
49 | System.out.println(error);
50 | }
51 | });
52 |
53 | builder.callback(
54 | new Callback() {
55 | @Override
56 | public void success(Message message) {
57 | System.out.println("Uploaded " + message);
58 | }
59 |
60 | @Override
61 | public void failure(Message message, Throwable throwable) {
62 | System.out.println("Could not upload " + message);
63 | System.out.println(throwable);
64 | }
65 | });
66 | }
67 | }
--------------------------------------------------------------------------------
/src/test/resources/segment-defaults.properties:
--------------------------------------------------------------------------------
1 | writeKey=SEGPROP-DEFAULT-normalKey
2 | debugWriteKey=SEGPROP-DEFAULT-debugKey
--------------------------------------------------------------------------------
/src/test/resources/segment.properties:
--------------------------------------------------------------------------------
1 | writeKey=SEGPROP-normal
2 | debugWriteKey=SEGPROP-debug
--------------------------------------------------------------------------------