├── .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 | [![Build status](https://github.com/redhat-developer/intellij-redhat-telemetry/workflows/Java%20CI%20with%20Gradle/badge.svg)](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 | ![Opt-in request](images/optin-request.png) 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 | ![Opt-in preferences](images/optin-preferences.png) 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 | ![Consumer-preferences panel](images/consumer-preferences.png) -------------------------------------------------------------------------------- /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>> timezones = new Lazy<>(() -> deserialize(TIMEZONES)); 45 | 46 | protected Country() { 47 | // for testing purposes 48 | } 49 | 50 | @Nullable 51 | public String get(TimeZone timeZone) { 52 | if (timeZone == null) { 53 | return null; 54 | } 55 | return get(timeZone.getID()); 56 | } 57 | 58 | @Nullable 59 | public String get(String timezoneId) { 60 | Map timezone = timezones.get().get(timezoneId); 61 | if (timezone == null) { 62 | return null; 63 | } 64 | String abbreviation = timezone.get(KEY_COUNTRY); 65 | if (abbreviation != null) { 66 | return abbreviation; 67 | } 68 | String alternative = timezone.get(KEY_ALTERNATIVE); 69 | if (alternative == null) { 70 | return null; 71 | } 72 | return get(alternative); 73 | } 74 | 75 | private Map deserialize(String file) { 76 | try { 77 | ObjectMapper mapper = new ObjectMapper(); 78 | InputStream input = getClass().getResourceAsStream(file); 79 | TypeReference> typeRef = new TypeReference>() {}; 80 | return mapper.readValue(input, typeRef); 81 | } catch (IOException e) { 82 | LOGGER.warn("Could not load file " + file, e); 83 | return new HashMap<>(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Environment.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.intellij.openapi.extensions.PluginDescriptor; 14 | 15 | import java.util.Locale; 16 | import java.util.Objects; 17 | import java.util.TimeZone; 18 | 19 | public class Environment { 20 | 21 | public static final String UNKNOWN_COUNTRY = "ZZ"; 22 | 23 | private final Plugin plugin; 24 | private final IDE ide; 25 | private final Platform platform; 26 | private final String timezone; 27 | private final String locale; 28 | private final String country; 29 | 30 | private Environment(Plugin plugin, IDE ide, Platform platform, String timezone, String locale, String country) { 31 | this.plugin = plugin; 32 | this.ide = ide; 33 | this.platform = platform; 34 | this.timezone = timezone; 35 | this.locale = locale; 36 | this.country = country; 37 | } 38 | 39 | public static class Builder { 40 | 41 | private IDE ide; 42 | private Plugin plugin; 43 | private Platform platform; 44 | private String timezone; 45 | private String locale; 46 | private String country; 47 | 48 | public Builder ide(IDE ide) { 49 | this.ide = ide; 50 | return this; 51 | } 52 | 53 | private void ensureIDE() { 54 | if (ide == null) { 55 | ide(new IDE.Factory().create()); 56 | } 57 | } 58 | 59 | public Buildable plugin(PluginDescriptor descriptor) { 60 | return plugin(new Plugin.Factory().create(descriptor)); 61 | } 62 | 63 | public Buildable plugin(Plugin plugin) { 64 | this.plugin = plugin; 65 | return new Buildable(); 66 | } 67 | 68 | public Builder platform(Platform platform) { 69 | this.platform = platform; 70 | return this; 71 | } 72 | 73 | private void ensurePlatform() { 74 | if (platform == null) { 75 | platform(new Platform()); 76 | } 77 | } 78 | 79 | public Builder timezone(String timezone) { 80 | this.timezone = timezone; 81 | return this; 82 | } 83 | 84 | private void ensureTimezone() { 85 | if (timezone == null) { 86 | timezone(TimeZone.getDefault().getID()); 87 | } 88 | } 89 | 90 | public Builder locale(String locale) { 91 | this.locale = locale; 92 | return this; 93 | } 94 | 95 | private void ensureLocale() { 96 | if (locale == null) { 97 | locale(Locale.getDefault().toString().replace('_', '-')); 98 | } 99 | } 100 | 101 | public Builder country(String country) { 102 | this.country = country; 103 | return this; 104 | } 105 | 106 | private void ensureCountry() { 107 | if (this.country == null) { 108 | /* 109 | * We're not allowed to query 3rd party services to determine the country. 110 | * Segment won't report countries for incoming requests. 111 | * We thus currently dont have any better solution than use the country in the Locale. 112 | */ 113 | ensureTimezone(); 114 | String country = Country.getInstance().get(timezone); 115 | if (country == null) { 116 | country = UNKNOWN_COUNTRY; 117 | } 118 | country(country); 119 | } 120 | } 121 | 122 | public class Buildable { 123 | public Environment build() { 124 | ensureIDE(); 125 | ensurePlatform(); 126 | ensureCountry(); 127 | ensureLocale(); 128 | ensureTimezone(); 129 | return new Environment(plugin, ide, platform, timezone, locale, country); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Returns the plugin from which Telemetry events are sent. 136 | */ 137 | public Plugin getPlugin() { 138 | return plugin; 139 | } 140 | 141 | /** 142 | * Returns the application from which Telemetry events are sent . 143 | */ 144 | public IDE getIde() { 145 | return ide; 146 | } 147 | 148 | /** 149 | * Returns the platform (or OS) from from which Telemetry events are sent. 150 | */ 151 | public Platform getPlatform() { 152 | return platform; 153 | } 154 | 155 | /** 156 | * Returns the user timezone, eg. 'Europe/Paris' 157 | */ 158 | public String getTimezone() { 159 | return timezone; 160 | } 161 | 162 | /** 163 | * Returns the user locale, eg. 'en-US' 164 | */ 165 | public String getLocale() { 166 | return locale; 167 | } 168 | 169 | public String getCountry() { 170 | return country; 171 | } 172 | 173 | @Override 174 | public boolean equals(Object o) { 175 | if (this == o) return true; 176 | if (!(o instanceof Environment)) return false; 177 | Environment that = (Environment) o; 178 | return Objects.equals(plugin, that.plugin) 179 | && Objects.equals(ide, that.ide) 180 | && Objects.equals(platform, that.platform) 181 | && Objects.equals(timezone, that.timezone) 182 | && Objects.equals(locale, that.locale) 183 | && Objects.equals(country, that.country); 184 | } 185 | 186 | @Override 187 | public int hashCode() { 188 | return Objects.hash(plugin, ide, platform, timezone, locale, country); 189 | } 190 | 191 | } 192 | 193 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Event.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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.HashMap; 14 | import java.util.Map; 15 | 16 | import static com.redhat.devtools.intellij.telemetry.core.service.Message.PROP_ERROR; 17 | 18 | public class Event { 19 | 20 | public enum Type { 21 | USER, ACTION, STARTUP, SHUTDOWN 22 | } 23 | 24 | private final Type type; 25 | private final String name; 26 | private final Map properties; 27 | 28 | public Event(Type type, String name) { 29 | this(type, name, new HashMap<>()); 30 | } 31 | 32 | public Event(Type type, String name, Map properties) { 33 | this.type = type; 34 | this.name = name; 35 | this.properties = properties; 36 | } 37 | 38 | public Type getType() { 39 | return type; 40 | } 41 | 42 | public String getName() { 43 | return name; 44 | } 45 | 46 | public Map getProperties() { 47 | return properties; 48 | } 49 | 50 | public boolean hasError() { 51 | return properties != null 52 | && properties.containsKey(PROP_ERROR); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackService.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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.intellij.openapi.diagnostic.Logger; 14 | 15 | class FeedbackService implements IService { 16 | 17 | private static final Logger LOGGER = Logger.getInstance(FeedbackService.class); 18 | 19 | protected final IMessageBroker broker; 20 | 21 | public FeedbackService(final IMessageBroker broker) { 22 | this.broker = broker; 23 | } 24 | 25 | @Override 26 | public void send(Event event) { 27 | broker.send(event); 28 | } 29 | 30 | public void dispose() { 31 | broker.dispose(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackServiceFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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.intellij.openapi.components.Service; 14 | import com.intellij.openapi.project.DumbAware; 15 | 16 | @Service 17 | final class FeedbackServiceFactory implements DumbAware { 18 | 19 | public IService create(IMessageBroker broker) { 20 | return new FeedbackService(broker); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/IDE.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; 12 | 13 | import com.intellij.openapi.application.ApplicationInfo; 14 | import com.intellij.openapi.application.ApplicationNamesInfo; 15 | 16 | public class IDE extends Application { 17 | 18 | public static final String PROP_JAVA_VERSION = "java_version"; 19 | 20 | public static final class Factory { 21 | public IDE create() { 22 | return new IDE( 23 | ApplicationNamesInfo.getInstance().getFullProductNameWithEdition(), 24 | ApplicationInfo.getInstance().getFullVersion()); 25 | } 26 | } 27 | 28 | IDE(String applicationName, String applicationVersion) { 29 | super(applicationName, applicationVersion); 30 | } 31 | 32 | public IDE setJavaVersion() { 33 | return setJavaVersion(System.getProperty("java.version")); 34 | } 35 | 36 | public IDE setJavaVersion(String version) { 37 | property(PROP_JAVA_VERSION, version); 38 | return this; 39 | } 40 | 41 | @Override 42 | public IDE property(String key, String value) { 43 | super.property(key, value); 44 | return this; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/IMessageBroker.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.intellij.openapi.extensions.PluginDescriptor; 14 | 15 | public interface IMessageBroker { 16 | void send(Event event); 17 | void dispose(); 18 | 19 | interface IMessageBrokerFactory { 20 | IMessageBroker create(boolean isDebug, Environment environment, PluginDescriptor descriptor); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/IService.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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 | public interface IService { 14 | void send(Event event); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Message.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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.intellij.openapi.diagnostic.Logger; 14 | 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | import static com.redhat.devtools.intellij.telemetry.core.util.AnonymizeUtils.anonymize; 19 | 20 | abstract class Message> { 21 | 22 | private static final Logger LOGGER = Logger.getInstance(Message.class); 23 | 24 | static final String PROP_RESULT = "result"; 25 | 26 | public static final String RESULT_SUCCESS = "success"; 27 | 28 | public static final String RESULT_ABORTED = "aborted"; 29 | 30 | static final String PROP_ERROR = "error"; 31 | 32 | private final Event.Type type; 33 | private final Map properties = new HashMap<>(); 34 | private final String name; 35 | private final IService service; 36 | 37 | protected Message(Event.Type type, String name, IService service) { 38 | this.name = name; 39 | this.type = type; 40 | this.service = service; 41 | } 42 | 43 | String getName() { 44 | return name; 45 | } 46 | 47 | Event.Type getType() { 48 | return type; 49 | } 50 | 51 | String getError() { 52 | return getProperty(PROP_ERROR); 53 | } 54 | 55 | public T error(Exception exception) { 56 | if (exception == null) { 57 | return (T) this; 58 | } 59 | return error(exception.getMessage()); 60 | } 61 | 62 | public T error(String message) { 63 | property(PROP_ERROR, anonymize(message)); 64 | return clearResult(); 65 | } 66 | 67 | protected T clearError() { 68 | properties().remove(PROP_ERROR); 69 | return (T) this; 70 | } 71 | 72 | String getResult() { 73 | return getProperty(PROP_RESULT); 74 | } 75 | 76 | public T result(String result) { 77 | property(PROP_RESULT, result); 78 | return clearError(); 79 | } 80 | 81 | public T success() { 82 | return result(RESULT_SUCCESS); 83 | } 84 | 85 | public T aborted() { 86 | return result(RESULT_ABORTED); 87 | } 88 | 89 | protected T clearResult() { 90 | properties().remove(PROP_RESULT); 91 | return (T) this; 92 | } 93 | 94 | public T property(String key, String value) { 95 | if (key == null 96 | || value == null) { 97 | LOGGER.warn("Ignored property with key: " + key + " value: " + value); 98 | } else { 99 | properties.put(key, value); 100 | } 101 | return (T) this; 102 | } 103 | 104 | String getProperty(String key) { 105 | return properties.get(key); 106 | } 107 | 108 | Map properties() { 109 | return properties; 110 | } 111 | 112 | protected boolean hasProperty(String key) { 113 | return properties.containsKey(key); 114 | } 115 | 116 | public Event send() { 117 | Event event = new Event(type, name, new HashMap<>(properties)); 118 | service.send(event); 119 | return event; 120 | } 121 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Platform.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.intellij.openapi.util.SystemInfo; 14 | 15 | import java.util.Objects; 16 | 17 | public class Platform { 18 | 19 | Platform() { 20 | /* 21 | * distribution not determined yet. 22 | * A possible impl exists in jbosstools-base/usage: 23 | * https://github.com/jbosstools/jbosstools-base/blob/master/usage/plugins/org.jboss.tools.usage/src/org/jboss/tools/usage/internal/environment/eclipse/LinuxSystem.java 24 | */ 25 | this(SystemInfo.OS_NAME, null, SystemInfo.OS_VERSION); 26 | } 27 | 28 | Platform(String name, String distribution, String version) { 29 | this.name = name; 30 | this.distribution = distribution; 31 | this.version = version; 32 | } 33 | 34 | private final String name; 35 | private final String distribution; 36 | private final String version; 37 | 38 | public String getName() { 39 | return name; 40 | } 41 | 42 | public String getDistribution() { 43 | return distribution; 44 | } 45 | 46 | public String getVersion() { 47 | return version; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) return true; 53 | if (!(o instanceof Platform)) return false; 54 | Platform platform = (Platform) o; 55 | return Objects.equals(name, platform.name) 56 | && Objects.equals(distribution, platform.distribution) 57 | && Objects.equals(version, platform.version); 58 | } 59 | 60 | @Override 61 | public int hashCode() { 62 | return Objects.hash(name, distribution, version); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Plugin.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; 12 | 13 | import com.intellij.openapi.extensions.PluginDescriptor; 14 | import com.sun.istack.NotNull; 15 | 16 | public class Plugin extends Application { 17 | 18 | public static final class Factory { 19 | public Plugin create(@NotNull PluginDescriptor descriptor) { 20 | return create(descriptor.getName(), descriptor.getVersion(), descriptor.getPluginId().getIdString()); 21 | } 22 | 23 | public Plugin create(String name, String version, String id) { 24 | return new Plugin(name, version, id); 25 | } 26 | } 27 | 28 | private final String id; 29 | 30 | Plugin(String name, String version, String id) { 31 | super(name, version); 32 | this.id = id; 33 | } 34 | 35 | @Override 36 | public Plugin property(String key, String value) { 37 | super.property(key, value); 38 | return this; 39 | } 40 | 41 | public String getId() { 42 | return id; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryService.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.intellij.openapi.application.ApplicationManager; 14 | import com.intellij.openapi.diagnostic.Logger; 15 | import com.intellij.util.messages.MessageBusConnection; 16 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; 17 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration.ConfigurationChangedListener; 18 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration.Mode; 19 | import com.redhat.devtools.intellij.telemetry.core.configuration.limits.IEventLimits; 20 | import com.redhat.devtools.intellij.telemetry.core.service.Event.Type; 21 | import com.redhat.devtools.intellij.telemetry.core.util.CircularBuffer; 22 | import com.redhat.devtools.intellij.telemetry.ui.TelemetryNotifications; 23 | 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | 26 | import static com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration.KEY_MODE; 27 | 28 | class TelemetryService implements IService { 29 | 30 | private static final Logger LOGGER = Logger.getInstance(TelemetryService.class); 31 | 32 | private static final int BUFFER_SIZE = 35; 33 | 34 | private final TelemetryNotifications notifications; 35 | private final TelemetryConfiguration configuration; 36 | private final IEventLimits limits; 37 | protected final IMessageBroker broker; 38 | private final AtomicBoolean userQueried = new AtomicBoolean(false); 39 | private final CircularBuffer onHold = new CircularBuffer<>(BUFFER_SIZE); 40 | 41 | public TelemetryService( 42 | final TelemetryConfiguration configuration, 43 | final IEventLimits limits, 44 | final IMessageBroker broker) { 45 | this(configuration, 46 | limits, 47 | broker, 48 | ApplicationManager.getApplication().getMessageBus().connect(), 49 | new TelemetryNotifications() 50 | ); 51 | } 52 | 53 | TelemetryService( 54 | final TelemetryConfiguration configuration, 55 | final IEventLimits limits, 56 | final IMessageBroker broker, 57 | final MessageBusConnection connection, 58 | final TelemetryNotifications notifications) { 59 | this.configuration = configuration; 60 | this.limits = limits; 61 | this.broker = broker; 62 | this.notifications = notifications; 63 | onConfigurationChanged(connection); 64 | } 65 | 66 | private void onConfigurationChanged(MessageBusConnection connection) { 67 | connection.subscribe(ConfigurationChangedListener.CONFIGURATION_CHANGED, (String key, String value) -> { 68 | if (KEY_MODE.equals(key) 69 | && Mode.safeValueOf(value).isEnabled()) { 70 | flushOnHold(); 71 | } 72 | }); 73 | } 74 | 75 | @Override 76 | public void send(Event event) { 77 | sendUserInfo(); 78 | doSend(event); 79 | queryUserConsent(); 80 | } 81 | 82 | private void sendUserInfo() { 83 | doSend(new Event( 84 | Type.USER, 85 | "Anonymous ID: " + UserId.INSTANCE.get())); 86 | } 87 | 88 | private void queryUserConsent() { 89 | if (!isConfigured() 90 | && userQueried.compareAndSet(false, true)) { 91 | notifications.queryUserConsent(); 92 | } 93 | } 94 | 95 | private void doSend(Event event) { 96 | if (isEnabled()) { 97 | flushOnHold(); 98 | if (limits.canSend(event)) { 99 | broker.send(event); 100 | limits.wasSent(event); 101 | } 102 | } else if (!isConfigured()) { 103 | onHold.offer(event); 104 | } 105 | } 106 | 107 | private boolean isEnabled() { 108 | return configuration != null 109 | && configuration.isEnabled(); 110 | } 111 | 112 | private boolean isConfigured() { 113 | return configuration != null 114 | && configuration.isConfigured(); 115 | } 116 | 117 | private void flushOnHold() { 118 | onHold.pollAll().forEach(this::send); 119 | } 120 | 121 | public void dispose() { 122 | flushOnHold(); 123 | onHold.clear(); 124 | broker.dispose(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceFactory.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.intellij.openapi.components.Service; 14 | import com.intellij.openapi.project.DumbAware; 15 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; 16 | import com.redhat.devtools.intellij.telemetry.core.configuration.limits.IEventLimits; 17 | 18 | @Service 19 | final class TelemetryServiceFactory implements DumbAware { 20 | 21 | public TelemetryService create(TelemetryConfiguration configuration, IEventLimits limits, IMessageBroker broker) { 22 | return new TelemetryService(configuration, limits, broker); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/UserId.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.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 com.redhat.devtools.intellij.telemetry.core.util.Lazy; 17 | 18 | import java.io.IOException; 19 | import java.nio.file.Files; 20 | import java.nio.file.Path; 21 | import java.util.UUID; 22 | import java.util.regex.Pattern; 23 | import java.util.stream.Stream; 24 | 25 | public class UserId { 26 | 27 | private static final Logger LOGGER = Logger.getInstance(UserId.class); 28 | 29 | public static final UserId INSTANCE = new UserId(); 30 | private static final Path UUID_FILE = Directories.RED_HAT.resolve("anonymousId"); 31 | private static final Pattern UUID_REGEX = 32 | Pattern.compile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"); 33 | private final Lazy uuid = new Lazy<>(() -> loadOrCreate(UUID_FILE)); 34 | private final Lazy percentile = new Lazy<>(this::createPercentile); 35 | 36 | /** for testing purposes */ 37 | protected UserId() {} 38 | 39 | public String get() { 40 | return uuid.get(); 41 | } 42 | 43 | private String loadOrCreate(Path file) { 44 | String uuid = null; 45 | if (exists(file)) { 46 | uuid = load(file); 47 | if (isValid(uuid)) { 48 | return uuid; 49 | } 50 | } 51 | uuid = create(); 52 | write(uuid, file); 53 | return uuid; 54 | } 55 | 56 | /** for testing purposes */ 57 | protected boolean exists(Path file) { 58 | return Files.exists(file); 59 | } 60 | 61 | /** for testing purposes */ 62 | protected String load(Path uuidFile) { 63 | String uuid = null; 64 | try(Stream lines = Files.lines(uuidFile)) { 65 | uuid = lines 66 | .findAny() 67 | .map(String::trim) 68 | .orElse(null); 69 | } catch (IOException e) { 70 | LOGGER.warn("Could not read redhat anonymous UUID file at " + uuidFile.toAbsolutePath(), e); 71 | } 72 | return uuid; 73 | } 74 | 75 | private boolean isValid(String uuid) { 76 | if (uuid == null) { 77 | return false; 78 | } 79 | return UUID_REGEX.matcher(uuid).matches(); 80 | } 81 | 82 | private String create() { 83 | return UUID.randomUUID().toString(); 84 | } 85 | 86 | /** for testing purposes */ 87 | protected void write(String uuid, Path uuidFile) { 88 | try { 89 | FileUtils.createFileAndParent(uuidFile); 90 | FileUtils.write(uuid, uuidFile); 91 | } catch (IOException e) { 92 | LOGGER.warn("Could not write redhat anonymous UUID to file at " + UUID_FILE.toAbsolutePath(), e); 93 | } 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | int hash = 0; 99 | String uuid = get(); 100 | for (int i = 0; i < uuid.length(); i++) { 101 | int code = uuid.codePointAt(i); 102 | hash = ((hash << 5) - hash) + code; 103 | } 104 | return hash; 105 | } 106 | 107 | public float getPercentile() { 108 | return percentile.get(); 109 | } 110 | 111 | private float createPercentile() { 112 | try { 113 | String hash = String.valueOf(Math.abs(hashCode())); 114 | int length = Math.min(4, hash.length()); 115 | return Float.parseFloat(hash.substring(hash.length() - length)) / 10000; // use at most last 4 chars 116 | } catch (NumberFormatException e) { 117 | return 0; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/ISegmentConfiguration.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 | public interface ISegmentConfiguration { 14 | String getNormalWriteKey(); 15 | String getDebugWriteKey(); 16 | 17 | default String getWriteKey(boolean isDebug) { 18 | if (isDebug) { 19 | return getDebugWriteKey(); 20 | } else { 21 | return getNormalWriteKey(); 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/IdentifyTraits.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 java.util.Objects; 14 | 15 | /** 16 | * Traits that the segment broker sends with am identify event. 17 | */ 18 | class IdentifyTraits { 19 | private final String locale; 20 | private final String timezone; 21 | private final String osName; 22 | private final String osVersion; 23 | private final String osDistribution; 24 | 25 | IdentifyTraits(String locale, String timezone, String osName, String osVersion, String osDistribution) { 26 | this.locale = locale; 27 | this.timezone = timezone; 28 | this.osName = osName; 29 | this.osVersion = osVersion; 30 | this.osDistribution = osDistribution; 31 | } 32 | 33 | public String getLocale() { 34 | return locale; 35 | } 36 | 37 | public String getTimezone() { 38 | return timezone; 39 | } 40 | 41 | public String getOsName() { 42 | return osName; 43 | } 44 | 45 | public String getOsVersion() { 46 | return osVersion; 47 | } 48 | 49 | public String getOsDistribution() { 50 | return osDistribution; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (!(o instanceof IdentifyTraits)) return false; 57 | IdentifyTraits identifyTraits = (IdentifyTraits) o; 58 | return Objects.equals(locale, identifyTraits.locale) 59 | && Objects.equals(timezone, identifyTraits.timezone) 60 | && Objects.equals(osName, identifyTraits.osName) 61 | && Objects.equals(osVersion, identifyTraits.osVersion) 62 | && Objects.equals(osDistribution, identifyTraits.osDistribution); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(locale, timezone, osName, osVersion, osDistribution); 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/IdentifyTraitsPersistence.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 com.intellij.openapi.diagnostic.Logger; 15 | import com.redhat.devtools.intellij.telemetry.core.util.Directories; 16 | import com.redhat.devtools.intellij.telemetry.core.util.FileUtils; 17 | 18 | import java.io.IOException; 19 | import java.nio.file.Files; 20 | import java.nio.file.Path; 21 | import java.util.Objects; 22 | import java.util.stream.Stream; 23 | 24 | /** 25 | * Persistency for {@link IdentifyTraits}. 26 | */ 27 | public class IdentifyTraitsPersistence { 28 | 29 | public static final IdentifyTraitsPersistence INSTANCE = new IdentifyTraitsPersistence(); 30 | private static final Logger LOGGER = Logger.getInstance(IdentifyTraitsPersistence.class); 31 | 32 | private static final Path FILE = Directories.RED_HAT.resolve("segment-identify-traits.json"); 33 | 34 | private IdentifyTraits identifyTraits = null; 35 | 36 | protected IdentifyTraitsPersistence() {} 37 | 38 | synchronized IdentifyTraits get() { 39 | if (identifyTraits == null) { 40 | this.identifyTraits = deserialize(load(FILE)); 41 | } 42 | return identifyTraits; 43 | } 44 | 45 | synchronized void set(IdentifyTraits identifyTraits) { 46 | if (Objects.equals(identifyTraits, this.identifyTraits)) { 47 | return; 48 | } 49 | this.identifyTraits = identifyTraits; 50 | String string = null; 51 | if (identifyTraits != null) { 52 | string = serialize(identifyTraits); 53 | } 54 | save(string, FILE); 55 | } 56 | 57 | private String serialize(IdentifyTraits identifyTraits) { 58 | if (identifyTraits == null) { 59 | return null; 60 | } 61 | return new Gson().toJson(identifyTraits); 62 | } 63 | 64 | private IdentifyTraits deserialize(String identity) { 65 | if (identity == null) { 66 | return null; 67 | } 68 | return new Gson().fromJson(identity, IdentifyTraits.class); 69 | } 70 | 71 | private String load(Path file) { 72 | String event = null; 73 | try(Stream lines = getLines(file)) { 74 | event = lines 75 | .findAny() 76 | .map(String::trim) 77 | .orElse(null); 78 | } catch (IOException e) { 79 | LOGGER.warn("Could not read identity file at " + file.toAbsolutePath(), e); 80 | } 81 | return event; 82 | } 83 | 84 | /* for testing purposes */ 85 | protected Stream getLines(Path file) throws IOException { 86 | return Files.lines(file); 87 | } 88 | 89 | private void save(String event, Path file) { 90 | try { 91 | createFileAndParent(file); 92 | writeFile(event, file); 93 | } catch (IOException e) { 94 | LOGGER.warn("Could not write identity to file at " + FILE.toAbsolutePath(), e); 95 | } 96 | } 97 | 98 | /* for testing purposes */ 99 | protected void createFileAndParent(Path file) throws IOException { 100 | FileUtils.createFileAndParent(file); 101 | } 102 | 103 | /* for testing purposes */ 104 | protected void writeFile(String event, Path file) throws IOException { 105 | FileUtils.write(event, file); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerFactory.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2023 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.intellij.openapi.extensions.PluginDescriptor; 14 | import com.redhat.devtools.intellij.telemetry.core.service.IMessageBroker; 15 | import com.redhat.devtools.intellij.telemetry.core.service.Environment; 16 | import com.redhat.devtools.intellij.telemetry.core.service.UserId; 17 | 18 | import static com.redhat.devtools.intellij.telemetry.core.service.IMessageBroker.*; 19 | 20 | public class SegmentBrokerFactory implements IMessageBrokerFactory { 21 | 22 | @Override 23 | public IMessageBroker create(boolean isDebug, Environment environment, PluginDescriptor descriptor) { 24 | SegmentConfiguration configuration = new SegmentConfiguration(descriptor.getPluginClassLoader()); 25 | return new SegmentBroker( 26 | isDebug, 27 | UserId.INSTANCE.get(), 28 | environment, 29 | configuration); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentConfiguration.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 com.redhat.devtools.intellij.telemetry.core.configuration.ClasspathConfiguration; 14 | import com.redhat.devtools.intellij.telemetry.core.configuration.CompositeConfiguration; 15 | import com.redhat.devtools.intellij.telemetry.core.configuration.IConfiguration; 16 | import com.redhat.devtools.intellij.telemetry.core.configuration.SystemProperties; 17 | 18 | import java.nio.file.Paths; 19 | import java.util.Arrays; 20 | import java.util.List; 21 | 22 | public class SegmentConfiguration extends CompositeConfiguration implements ISegmentConfiguration { 23 | 24 | public static final String KEY_SEGMENT_WRITE = "writeKey"; 25 | public static final String KEY_SEGMENT_DEBUG_WRITE = "debugWriteKey"; 26 | 27 | private static final String SEGMENT_PROPERTIES = "segment.properties"; 28 | private static final String SEGMENT_DEFAULTS_PROPERTIES = "segment-defaults.properties"; 29 | 30 | private final ClasspathConfiguration consumerClasspathConfiguration; 31 | 32 | SegmentConfiguration(ClassLoader classLoader) { 33 | this(new ClasspathConfiguration(Paths.get(SEGMENT_PROPERTIES), classLoader)); 34 | } 35 | 36 | SegmentConfiguration(ClasspathConfiguration consumerClasspathConfiguration) { 37 | this.consumerClasspathConfiguration = consumerClasspathConfiguration; 38 | } 39 | 40 | @Override 41 | public void put(String key, String value) { 42 | consumerClasspathConfiguration.put(key, value); 43 | } 44 | 45 | @Override 46 | public List getConfigurations() { 47 | return Arrays.asList( 48 | new SystemProperties(), 49 | // segment.properties in consuming plugin 50 | consumerClasspathConfiguration, 51 | // segment-defaults.properties in this plugin 52 | new ClasspathConfiguration(Paths.get(SEGMENT_DEFAULTS_PROPERTIES), getClass().getClassLoader())); 53 | } 54 | 55 | @Override 56 | public String getNormalWriteKey() { 57 | return get(KEY_SEGMENT_WRITE); 58 | } 59 | 60 | @Override 61 | public String getDebugWriteKey() { 62 | return get(KEY_SEGMENT_DEBUG_WRITE); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/package-info.java: -------------------------------------------------------------------------------- 1 | @ApiStatus.Internal 2 | package com.redhat.devtools.intellij.telemetry.core.service.segment; 3 | 4 | import org.jetbrains.annotations.ApiStatus; -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtils.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 java.util.regex.Pattern; 14 | 15 | public class AnonymizeUtils { 16 | 17 | public static final String USER_NAME = System.getProperty("user.name"); 18 | public static final String ANONYMOUS_USER_NAME = ""; 19 | public static final String HOME_DIR = System.getProperty("user.home"); 20 | public static final String ANONYMOUS_HOMEDIR = ""; 21 | public static final String TMP_DIR = System.getProperty("java.io.tmpdir"); 22 | public static final String ANONYMOUS_TMPDIR = ""; 23 | private static final Pattern EMAIL_PATTERN = Pattern.compile( 24 | "[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}", 25 | Pattern.CASE_INSENSITIVE); 26 | public static final String ANONYMOUS_EMAIL = ""; 27 | private static final Pattern IP_PATTERN = Pattern.compile( 28 | "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])"); 29 | public static final String ANONYMOUS_IP = ""; 30 | public static final String ANONYMOUS_RESOURCENAME = ""; 31 | public static final String ANONYMOUS_NAMESPACE = ""; 32 | 33 | private AnonymizeUtils() { 34 | } 35 | 36 | public static String anonymize(String string) { 37 | return anonymizeEmail( 38 | anonymizeUserName( 39 | anonymizeIP( 40 | anonymizeHomeDir( 41 | anonymizeTmpDir(string) 42 | ) 43 | ) 44 | ) 45 | ); 46 | } 47 | 48 | public static String anonymizeResource(String name, String namespace, String string) { 49 | if (string == null 50 | || string.isEmpty()) { 51 | return string; 52 | } 53 | if (name != null) { 54 | string = string.replace(name, ANONYMOUS_RESOURCENAME); 55 | } 56 | if (namespace != null) { 57 | string = string.replace(namespace, ANONYMOUS_NAMESPACE); 58 | } 59 | return string; 60 | } 61 | 62 | public static String anonymizeUserName(String string) { 63 | if (string == null 64 | || string.isEmpty()) { 65 | return string; 66 | } 67 | return string.replace(USER_NAME, ANONYMOUS_USER_NAME); 68 | } 69 | 70 | public static String anonymizeEmail(String string) { 71 | if (string == null 72 | || string.isEmpty()) { 73 | return string; 74 | } 75 | return EMAIL_PATTERN.matcher(string).replaceAll(ANONYMOUS_EMAIL); 76 | } 77 | 78 | public static String anonymizeHomeDir(String string) { 79 | if (string == null 80 | || string.isEmpty()) { 81 | return string; 82 | } 83 | return string.replace(HOME_DIR, ANONYMOUS_HOMEDIR); 84 | } 85 | 86 | public static String anonymizeTmpDir(String string) { 87 | if (string == null 88 | || string.isEmpty()) { 89 | return string; 90 | } 91 | return string.replace(TMP_DIR, ANONYMOUS_TMPDIR); 92 | } 93 | 94 | public static String anonymizeIP(String string) { 95 | if (string == null 96 | || string.isEmpty()) { 97 | return string; 98 | } 99 | return IP_PATTERN.matcher(string).replaceAll(ANONYMOUS_IP); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/CircularBuffer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | *

4 | * Copyright (c) 2017 Eugen Paraschiv 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 | * Based on work by Eugen Paraschiv for Baeldung.com at 25 | * https://github.com/eugenp/tutorials/blob/master/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java 26 | */ 27 | package com.redhat.devtools.intellij.telemetry.core.util; 28 | 29 | import org.jetbrains.annotations.Nullable; 30 | 31 | import java.util.ArrayList; 32 | import java.util.Arrays; 33 | import java.util.List; 34 | 35 | public class CircularBuffer { 36 | 37 | private static final int DEFAULT_CAPACITY = 8; 38 | 39 | private final int capacity; 40 | private final E[] data; 41 | private volatile int writeSequence = -1; 42 | private volatile int readSequence = 0; 43 | 44 | @SuppressWarnings("unchecked") 45 | public CircularBuffer(int capacity) { 46 | this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; 47 | this.data = (E[]) new Object[capacity]; 48 | } 49 | 50 | public boolean offer(E element) { 51 | if (!isFull()) { 52 | int nextWriteSeq = writeSequence + 1; 53 | data[nextWriteSeq % capacity] = element; 54 | 55 | writeSequence++; 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | @Nullable 62 | public E poll() { 63 | if (!isEmpty()) { 64 | E nextValue = data[readSequence % capacity]; 65 | readSequence++; 66 | return nextValue; 67 | } 68 | 69 | return null; 70 | } 71 | 72 | public List pollAll() { 73 | List values = new ArrayList<>(); 74 | for(E value = poll(); value != null; value = poll()) { 75 | values.add(value); 76 | } 77 | return values; 78 | } 79 | 80 | public int capacity() { 81 | return capacity; 82 | } 83 | 84 | public int size() { 85 | return (writeSequence - readSequence) + 1; 86 | } 87 | 88 | public boolean isEmpty() { 89 | return writeSequence < readSequence; 90 | } 91 | 92 | public boolean isFull() { 93 | return size() >= capacity; 94 | } 95 | 96 | public void clear() { 97 | readSequence = 0; 98 | writeSequence = -1; 99 | Arrays.fill(data, null); 100 | } 101 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Directories.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.util; 12 | 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | public class Directories { 17 | 18 | public static final Path RED_HAT = Paths.get( 19 | System.getProperty("user.home"), 20 | ".redhat"); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/FileUtils.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.util; 12 | 13 | import com.intellij.openapi.util.text.StringUtil; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.Writer; 20 | import java.net.MalformedURLException; 21 | import java.net.URI; 22 | import java.net.URISyntaxException; 23 | import java.net.URL; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | 27 | public class FileUtils { 28 | 29 | private static final String FILE_URL_PREFIX = "file:"; 30 | 31 | private FileUtils() { 32 | } 33 | 34 | /** 35 | * Creates the file for the given path and the folder that contains it. 36 | * Does nothing if it any of those already exist. 37 | * 38 | * @param file the file to create 39 | * 40 | * @throws IOException if the file operation fails 41 | */ 42 | public static void createFileAndParent(Path file) throws IOException { 43 | if (!Files.exists(file.getParent())) { 44 | Files.createDirectories(file.getParent()); 45 | } 46 | if (!Files.exists(file)) { 47 | Files.createFile(file); 48 | } 49 | } 50 | 51 | public static Path ensureExists(@NotNull Path path) throws IOException { 52 | FileUtils.createFileAndParent(path); 53 | return path; 54 | } 55 | 56 | public static void write(String content, Path file) throws IOException { 57 | try (Writer writer = Files.newBufferedWriter(file)) { 58 | writer.append(content); 59 | } 60 | } 61 | 62 | public static boolean isFileUrl(String url) { 63 | return !StringUtil.isEmpty(url) 64 | && url.startsWith(FILE_URL_PREFIX); 65 | } 66 | 67 | @Nullable 68 | public static Path getPathForFileUrl(String url) { 69 | if (!isFileUrl(url)) { 70 | return null; 71 | } 72 | try { 73 | URI uri = new URL(url).toURI(); 74 | return new File(uri).toPath(); 75 | } catch (MalformedURLException | URISyntaxException e) { 76 | return null; 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Lazy.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 java.util.function.Supplier; 14 | 15 | public class Lazy implements Supplier { 16 | 17 | private final Supplier factory; 18 | private volatile T value; 19 | 20 | public Lazy(Supplier factory) { 21 | this.factory = factory; 22 | } 23 | 24 | @Override 25 | public T get() { 26 | if (value == null) { 27 | this.value = factory.get(); 28 | onCreated(value); 29 | } 30 | return value; 31 | } 32 | 33 | protected void onCreated(T value) { 34 | // override to customized 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/MapBuilder.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.util; 12 | 13 | import java.util.AbstractMap; 14 | import java.util.Collection; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | public class MapBuilder { 19 | 20 | private final Map map = new HashMap<>(); 21 | 22 | public MapBuilder pair(String key, String value) { 23 | map.put(key, value); 24 | return this; 25 | } 26 | 27 | public MapBuilder pair(String key, Object value) { 28 | map.put(key, value); 29 | return this; 30 | } 31 | 32 | public MapBuilder pairs(Collection> entries) { 33 | if (entries == null) { 34 | return this; 35 | } 36 | entries.stream().forEach(entry -> map.put(entry.getKey(), entry.getValue())); 37 | return this; 38 | } 39 | 40 | public MapValueBuilder mapPair(String key) { 41 | return new MapValueBuilder(key); 42 | } 43 | 44 | public Map build() { 45 | return map; 46 | } 47 | 48 | public class MapValueBuilder { 49 | private final Map map = new HashMap<>(); 50 | private final String key; 51 | 52 | private MapValueBuilder(String key) { 53 | this.key = key; 54 | } 55 | 56 | public MapValueBuilder pair(String key, Object value) { 57 | map.put(key, value); 58 | return this; 59 | } 60 | 61 | public MapValueBuilder pairs(Collection> entries) { 62 | if (entries == null) { 63 | return this; 64 | } 65 | entries.stream().forEach(entry -> map.put(entry.getKey(), entry.getValue())); 66 | return this; 67 | } 68 | 69 | public MapBuilder build() { 70 | MapBuilder.this.pair(key, map); 71 | return MapBuilder.this; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/core/util/TimeUtils.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.jetbrains.annotations.Nullable; 14 | 15 | import java.time.Duration; 16 | import java.time.Instant; 17 | import java.time.LocalDate; 18 | import java.time.LocalDateTime; 19 | import java.time.LocalTime; 20 | import java.time.Period; 21 | import java.time.ZoneId; 22 | import java.time.ZonedDateTime; 23 | import java.time.temporal.ChronoUnit; 24 | import java.util.Date; 25 | import java.util.regex.Matcher; 26 | import java.util.regex.Pattern; 27 | 28 | public class TimeUtils { 29 | 30 | private static final Pattern HH_MM_SS_DURATION = Pattern.compile("([\\d]+):([\\d]{2}):([\\d]{2})"); 31 | 32 | private TimeUtils() {} 33 | 34 | /** 35 | * Returns a {@link LocalTime} for a given milliseconds. 36 | * 37 | * @param millis 38 | * @return 39 | */ 40 | public static LocalDateTime toLocalTime(long millis) { 41 | Instant instant = new Date(millis).toInstant(); 42 | ZonedDateTime time = instant.atZone(ZoneId.systemDefault()); 43 | return time.toLocalDateTime(); 44 | } 45 | 46 | /** 47 | * Returns a human readable string representation in the format "HH:MM:SS" for a given duration. 48 | * 49 | * @param duration to be transformed to its human readable form 50 | * 51 | * @return a string "HH:MM:SS" representing the duration 52 | */ 53 | public static String toString(Duration duration) { 54 | return String.format("%02d:%02d:%02d", 55 | duration.toHours(), 56 | duration.toMinutes() % 60, 57 | duration.getSeconds() % 60); 58 | } 59 | 60 | /** 61 | * Returns the duration for a given string "HH:MM:SS". 62 | * It's the reverse operation for #toString(Duration). 63 | * 64 | * @param hoursMinutesSeconds 65 | * @return the duration 66 | * 67 | * @see #toString(Duration) 68 | */ 69 | @Nullable 70 | public static Duration toDuration(String hoursMinutesSeconds) { 71 | Matcher matcher = HH_MM_SS_DURATION.matcher(hoursMinutesSeconds); 72 | if (!matcher.matches()) { 73 | return null; 74 | } 75 | long hours = Integer.parseInt(matcher.group(1)); 76 | Duration duration = Duration.ofHours(hours); 77 | long minutes = Integer.parseInt(matcher.group(2)); 78 | duration = duration.plusMinutes(minutes); 79 | long seconds = Integer.parseInt(matcher.group(3)); 80 | duration = duration.plusSeconds(seconds); 81 | return duration; 82 | } 83 | 84 | /** 85 | * Returns {@code true} if the given {@link LocalDate} is today. 86 | * Returns {@code false} otherwise. 87 | * 88 | * @param dateTime the date/time to check whether it's today. 89 | * 90 | * @return true if the given date/time is today. 91 | */ 92 | public static boolean isToday(LocalDateTime dateTime) { 93 | return ChronoUnit.DAYS.between(dateTime.toLocalDate(), LocalDate.now()) == 0; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/TelemetryNotifications.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.ui; 12 | 13 | import com.intellij.icons.AllIcons; 14 | import com.intellij.notification.Notification; 15 | import com.intellij.notification.NotificationDisplayType; 16 | import com.intellij.notification.NotificationGroup; 17 | import com.intellij.notification.NotificationListener; 18 | import com.intellij.notification.NotificationType; 19 | import com.intellij.openapi.diagnostic.Logger; 20 | import com.intellij.openapi.project.DumbAwareAction; 21 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; 22 | import com.redhat.devtools.intellij.telemetry.ui.utils.NotificationGroupFactory; 23 | 24 | import java.io.IOException; 25 | 26 | public class TelemetryNotifications { 27 | 28 | private static final Logger LOGGER = Logger.getInstance(TelemetryNotifications.class); 29 | 30 | private static final NotificationGroup QUERY_USER_CONSENT = NotificationGroupFactory.create( 31 | "Enable Telemetry", 32 | NotificationDisplayType.STICKY_BALLOON, 33 | true); 34 | 35 | private final NotificationGroup group; 36 | 37 | public TelemetryNotifications() { 38 | this(QUERY_USER_CONSENT); 39 | } 40 | 41 | TelemetryNotifications(NotificationGroup group) { 42 | this.group = group; 43 | } 44 | 45 | public void queryUserConsent() { 46 | Notification notification = group.createNotification( 47 | "Help Red Hat improve its extensions by allowing them to collect anonymous usage data. " + 48 | "Read our privacy statement " + 49 | "and learn how to opt out.", 50 | NotificationType.INFORMATION); 51 | notification.setTitle("Enable Telemetry"); 52 | notification.setListener(new NotificationListener.UrlOpeningListener(false)); 53 | DumbAwareAction accept = DumbAwareAction.create("Accept", 54 | e -> enableTelemetry(true, notification)); 55 | DumbAwareAction deny = DumbAwareAction.create("Deny", 56 | e -> enableTelemetry(false, notification)); 57 | DumbAwareAction later = DumbAwareAction.create("Later", 58 | e -> notification.expire()); 59 | notification 60 | .addAction(accept) 61 | .addAction(deny) 62 | .addAction(later) 63 | .setIcon(AllIcons.General.TodoQuestion) 64 | .notify(null); 65 | } 66 | 67 | private static void enableTelemetry(boolean enabled, Notification notification) { 68 | TelemetryConfiguration configuration = TelemetryConfiguration.getInstance(); 69 | configuration.setEnabled(enabled); 70 | try { 71 | configuration.save(); 72 | } catch (IOException e) { 73 | LOGGER.warn("Could not save telemetry configuration.", e); 74 | } finally { 75 | notification.expire(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/preferences/TelemetryComponent.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.ui.preferences; 12 | 13 | import com.intellij.ui.components.JBCheckBox; 14 | import com.intellij.util.ui.FormBuilder; 15 | import com.intellij.util.ui.UI; 16 | 17 | import javax.swing.JComponent; 18 | import javax.swing.JPanel; 19 | 20 | /** 21 | * Provides a {@link JPanel} with widgets for the Telemetry settings. 22 | */ 23 | public class TelemetryComponent { 24 | 25 | private static final String DESCRIPTION = 26 | "Help Red Hat improve its products by sending anonymous data about features and plugins used, " 27 | + "hardware and software configuration.
" 28 | + "
" 29 | + "Please note that this will not include personal data or any sensitive Information.
" 30 | + "The data sent complies with the Red Hat Privacy Policy."; 31 | 32 | private final JPanel panel; 33 | private final JBCheckBox enabled = new JBCheckBox("Send usage statistics"); 34 | 35 | public TelemetryComponent() { 36 | this.panel = FormBuilder.createFormBuilder() 37 | .addComponent(createCommentedPanel(enabled, DESCRIPTION), 1) 38 | .addComponentFillVertically(new JPanel(), 0) 39 | .getPanel(); 40 | } 41 | 42 | private JPanel createCommentedPanel(JComponent component, String comment) { 43 | // @See com.intellij.internal.ui.ComponentPanelTestAction for more details on how to create comment panels 44 | return UI.PanelFactory.panel(component) 45 | .withComment(comment) 46 | .createPanel(); 47 | } 48 | 49 | public JPanel getPanel() { 50 | return panel; 51 | } 52 | 53 | public JComponent getPreferredFocusedComponent() { 54 | return enabled; 55 | } 56 | 57 | public boolean isEnabled() { 58 | return enabled.isSelected(); 59 | } 60 | 61 | public void setEnabled(boolean enabled) { 62 | this.enabled.setSelected(enabled); 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/preferences/TelemetryConfigurable.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.ui.preferences; 12 | 13 | import com.intellij.openapi.diagnostic.Logger; 14 | import com.intellij.openapi.options.SearchableConfigurable; 15 | import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; 16 | import org.jetbrains.annotations.Nls; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import javax.swing.JComponent; 21 | import java.io.IOException; 22 | 23 | /** 24 | * Controller for telemetry settings. 25 | */ 26 | public class TelemetryConfigurable implements SearchableConfigurable { 27 | 28 | /* plugin.xml > applicationConfigurable > id */ 29 | public static final String ID = "tools.preferences.redhat.telemetry"; 30 | 31 | private static final Logger LOGGER = Logger.getInstance(TelemetryConfigurable.class); 32 | 33 | private TelemetryComponent component; 34 | private final TelemetryConfiguration configuration = TelemetryConfiguration.getInstance(); 35 | 36 | @Nls(capitalization = Nls.Capitalization.Title) 37 | @Override 38 | public String getDisplayName() { 39 | return "Red Hat Telemetry"; 40 | } 41 | 42 | @Override 43 | public JComponent getPreferredFocusedComponent() { 44 | return component.getPreferredFocusedComponent(); 45 | } 46 | 47 | @Nullable 48 | @Override 49 | public JComponent createComponent() { 50 | this.component = new TelemetryComponent(); 51 | return component.getPanel(); 52 | } 53 | 54 | @Override 55 | public boolean isModified() { 56 | boolean modified = false; 57 | modified |= (component.isEnabled() != configuration.isEnabled()); 58 | return modified; 59 | } 60 | 61 | @Override 62 | public void apply() { 63 | configuration.setEnabled(component.isEnabled()); 64 | try { 65 | configuration.save(); 66 | } catch (IOException e) { 67 | LOGGER.warn("Could not save telemetry configuration.", e); 68 | } 69 | } 70 | 71 | @Override 72 | public void reset() { 73 | component.setEnabled(configuration.isEnabled()); 74 | } 75 | 76 | @Override 77 | public void disposeUIResources() { 78 | component = null; 79 | } 80 | 81 | @NotNull 82 | @Override 83 | public String getId() { 84 | return ID; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/preferences/TelemetryPreferencesUtils.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.ui.preferences; 12 | 13 | import com.intellij.ide.DataManager; 14 | import com.intellij.openapi.options.Configurable; 15 | import com.intellij.openapi.options.ex.Settings; 16 | import com.intellij.ui.components.JBLabel; 17 | 18 | import javax.swing.JLabel; 19 | import javax.swing.JPanel; 20 | import javax.swing.SwingConstants; 21 | import javax.swing.event.HyperlinkEvent; 22 | import javax.swing.event.HyperlinkListener; 23 | 24 | import java.util.function.Supplier; 25 | 26 | import static com.redhat.devtools.intellij.telemetry.ui.utils.JBLabelUtils.toStyledHtml; 27 | 28 | public class TelemetryPreferencesUtils { 29 | 30 | private TelemetryPreferencesUtils() { 31 | } 32 | 33 | public static JLabel createTelemetryComponent(final String plugin, final Supplier panel) { 34 | JLabel component = new JBLabel("") { 35 | @Override 36 | protected HyperlinkListener createHyperlinkListener() { 37 | return event -> { 38 | if (HyperlinkEvent.EventType.ACTIVATED == event.getEventType()) { 39 | openTelemetryPreferences(panel); 40 | } 41 | }; 42 | } 43 | } 44 | .setCopyable(true) 45 | .setAllowAutoWrapping(true); 46 | component.setVerticalTextPosition(SwingConstants.TOP); 47 | component.setFocusable(false); 48 | component.setText(toStyledHtml( 49 | "
Help Red Hat improve " + plugin + " by sending anonymous usage data." 50 | + " You can enable or disable it in the preferences for Red Hat Telemetry." 51 | , 70, 52 | component)); 53 | return component; 54 | } 55 | 56 | private static void openTelemetryPreferences(Supplier panel) { 57 | Settings allSettings = Settings.KEY.getData(DataManager.getInstance().getDataContext(panel.get())); 58 | if (allSettings != null) { 59 | final Configurable configurable = allSettings.find(TelemetryConfigurable.ID); 60 | if (configurable != null) { 61 | allSettings.select(configurable); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/JBLabelUtils.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. 10 | ******************************************************************************/ 11 | package com.redhat.devtools.intellij.telemetry.ui.utils; 12 | 13 | import com.intellij.openapi.application.ApplicationInfo; 14 | import com.intellij.openapi.diagnostic.Logger; 15 | import com.intellij.ui.ColorUtil; 16 | 17 | import javax.swing.JLabel; 18 | import java.awt.Color; 19 | import java.lang.reflect.Field; 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | 23 | public class JBLabelUtils { 24 | 25 | private static final Logger LOGGER = Logger.getInstance(JBLabelUtils.class); 26 | 27 | private JBLabelUtils() { 28 | } 29 | 30 | /** 31 | * Turns the given text to html that a JBLabel can display (with links). 32 | * 33 | * @param text the text to convert to Html 34 | * @param maxLineLength the maximum number of characters in a line 35 | * @param component the component to retrieve the font metrics from 36 | * 37 | * @returns the text 38 | */ 39 | public static String toStyledHtml(String text, int maxLineLength, JLabel component) { 40 | String css = "\n"; 54 | int width = component.getFontMetrics(component.getFont()).stringWidth(text.substring(0, maxLineLength)); 55 | return "" + css + "

" + text + "
"; 56 | } 57 | 58 | private static Color getColor(String methodClass, String method, String fieldClass, String field) { 59 | try { 60 | // < IC-2022.1 61 | Class clazz = Class.forName(methodClass); 62 | Method colorMethod = clazz.getMethod(method); 63 | return (Color) colorMethod.invoke(null); 64 | } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | ClassCastException e) { 65 | // >= IC-2022.1 66 | try { 67 | Class clazz = Class.forName(fieldClass); 68 | Field colorField = clazz.getDeclaredField(field); 69 | return (Color) colorField.get(null); 70 | } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException | ClassCastException ex) { 71 | LOGGER.warn("Could not retrieve link colors from " 72 | + methodClass + "/" + method + " or " + fieldClass + "/" + field 73 | + "on IC-" + ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode(), ex); 74 | } 75 | return null; 76 | } 77 | } 78 | 79 | private static String toHex(Color color) { 80 | if (color == null) { 81 | return ""; 82 | } 83 | return ColorUtil.toHex(color); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/NotificationGroupFactory.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.ui.utils; 12 | 13 | import com.intellij.notification.NotificationDisplayType; 14 | import com.intellij.notification.NotificationGroup; 15 | import com.intellij.openapi.application.ApplicationInfo; 16 | import com.intellij.openapi.diagnostic.Logger; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.lang.reflect.Constructor; 20 | import java.lang.reflect.InvocationTargetException; 21 | import java.lang.reflect.Method; 22 | 23 | /** 24 | * A factory to create {@link NotificationGroup} on >= IC-2019.3 and >= IC-2021.3 where the API was broken. 25 | * 26 | *
    27 | *
  • IC-2019.3 28 | *
    {@code
    29 |  *          new NotificationGroup(String displayId, NotificationDisplayType defaultDisplayType, boolean logByDefault)
    30 |  *      }
    31 | *
  • 32 | *
  • IC-2021.3 33 | *
    {@code
    34 |  *          NotificationGroupManager.getInstance().getNotificationGroup(displayId)
    35 |  *      }
    36 | *
  • 37 | *
38 | */ 39 | public class NotificationGroupFactory { 40 | 41 | private NotificationGroupFactory() {} 42 | 43 | @Nullable 44 | public static NotificationGroup create(String displayId, NotificationDisplayType type, boolean logByDefault) { 45 | try { 46 | // < IC-2021.3 47 | // new NotificationGroup(String displayId, NotificationDisplayType defaultDisplayType, boolean logByDefault) 48 | Constructor constructor = NotificationGroup.class.getConstructor(String.class, NotificationDisplayType.class, boolean.class); 49 | return constructor.newInstance(displayId, type, logByDefault); 50 | } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { 51 | // >= IC-2021.3 52 | // constructor removed >= 2021.3 53 | try { 54 | // NotificationGroupManager.getInstance().getNotificationGroup(displayId) 55 | Class managerClass = Class.forName("com.intellij.notification.NotificationGroupManager"); 56 | Method getInstance = managerClass.getMethod("getInstance"); 57 | Object manager = getInstance.invoke(null); 58 | if (manager == null) { 59 | return null; 60 | } 61 | Method getNotificationGroup = managerClass.getMethod("getNotificationGroup", String.class); 62 | Object group = getNotificationGroup.invoke(manager, displayId); 63 | if (!(group instanceof NotificationGroup)) { 64 | return null; 65 | } 66 | return (NotificationGroup) group; 67 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { 68 | Logger.getInstance(NotificationGroupFactory.class).warn("Could not create NotificationGroup for IC-" 69 | + ApplicationInfo.getInstance().getBuild().asStringWithoutProductCode()); 70 | return null; 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.redhat.devtools.intellij.telemetry 3 | Telemetry by Red Hat 4 | Red-Hat 5 | 6 | A plugin that provides Telemetry APIs to be used by Red Hat IDEA plugins so that they can report user interactions. 8 | Those allow us to identify problems and improve our products.

9 | ]]> 10 |
11 | 12 | 1.2.1

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 --------------------------------------------------------------------------------