rudder_analytics_sync 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 981d6ac024bd76221c88351c38efd68185f8b237150394356b61ac83aff30b92
4
- data.tar.gz: fe80c13d8e4422e34e5a2e66254764df8819d5ba33a289a005ab1b41ec5d3702
3
+ metadata.gz: b5d4678d8449c5e3b0e80fe3607a93bafee2bb8a2224691b78943b409833d670
4
+ data.tar.gz: 4dd1fe50a69cc240bfe2217901ac26b6b3e14981410ad653a0e5e050a3d3d710
5
5
  SHA512:
6
- metadata.gz: 7bab5d567b4bbed93acdedeb446206726e705f1b43217961ec8da8ae30536b8fd178e62432edeef2f913509600521939fa5eda07c574d4ecae62779b7eaf8b8c
7
- data.tar.gz: 4d8881107ec8393004f59883efc61f6541b669a9626c787e1ed6aa2594bfad5ca79e9e4913f8c1546f8cccaf8f8b99698fb615b96953b8f11ff8bb4020b1d4cf
6
+ metadata.gz: 195d4b2ba1ac36ab16febe978dc73e18b0320e33dfddd1cdfe24e85b282c03f76354ffa3ed2cf4324020368733e61bc7870558591ecd70f7aed997b5d0fd0a72
7
+ data.tar.gz: 6deaf40d63b6ef1850ade76fc649b214f0ff2adc7c6996fe455972650285c98bc8292de75cf522c9c9ff4ad96e4750925cc645120a71d865d34f82ceb3d81ef4
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: "BUG : <Title>"
5
+ labels: bug, open source
6
+ assignees: pallabmaiti
7
+ ---
8
+
9
+ **Describe the bug**
10
+ Please provide the following information:
11
+
12
+ 1. A clear and concise description of what the bug is
13
+ 2. Share the event payload
14
+ 3. Offer a minimal viable example to reproduce the issue
15
+ 4. Include the error's stack trace
16
+ 5. Mention the date when the issue began
17
+
18
+ **To Reproduce**
19
+ Steps to reproduce the behaviour:
20
+
21
+ 1. Initialise Ruby SDK
22
+ 2. Make events '....'
23
+ 3. See the error
24
+
25
+ **Expected behavior**
26
+ A clear and concise description of what you expected to happen.
27
+
28
+ **Screenshots**
29
+ If applicable, add screenshots to help explain your problem.
30
+
31
+ **Version of the _Ruby_**
32
+ Please mention the version of the Ruby version you are using (e.g., Ruby v3.2.1).
33
+
34
+ **Version of the _Rudder Ruby_ SDK**
35
+ Please mention the version of the Rudder Ruby SDK you are using (e.g., Rudder Ruby SDK v1.0.0).
36
+
37
+ **SDK initialisation snippet**
38
+ Share the code snippet used for initializing the Ruby SDK.
39
+
40
+ **Check for Correct Usage of _writeKey_ and _dataPlaneUrl_**
41
+ Confirm that the correct `writeKey` and `dataPlaneUrl` are utilized during SDK initialization.
@@ -4,14 +4,22 @@ on:
4
4
  branches: ['master']
5
5
  types: ['opened', 'reopened', 'synchronize']
6
6
 
7
+ permissions:
8
+ contents: read
9
+
7
10
  jobs:
8
11
  build:
9
12
  runs-on: ubuntu-latest
10
13
 
11
14
  steps:
12
- - uses: actions/checkout@v3
15
+ - name: Harden the runner (Audit all outbound calls)
16
+ uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
17
+ with:
18
+ egress-policy: audit
19
+
20
+ - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
13
21
  - name: Set up Ruby
14
- uses: ruby/setup-ruby@v1
22
+ uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
15
23
  with:
16
24
  ruby-version: '2.7'
17
25
  bundler-cache: true
@@ -28,4 +36,4 @@ jobs:
28
36
  # - name: Install rubocop-rails
29
37
  # run: gem install rubocop-rails
30
38
  # - name: Execute linting
31
- # run: bundle exec rubocop
39
+ # run: bundle exec rubocop
@@ -4,13 +4,21 @@ on:
4
4
  branches: ['master']
5
5
  types: ['opened', 'reopened', 'edited', 'synchronize']
6
6
 
7
+ permissions:
8
+ contents: read
9
+
7
10
  jobs:
8
11
  check_pr_title:
9
12
  name: Check PR title
10
13
  runs-on: ubuntu-latest
11
14
  steps:
15
+ - name: Harden the runner (Audit all outbound calls)
16
+ uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
17
+ with:
18
+ egress-policy: audit
19
+
12
20
  - name: Checkout source branch
13
- uses: actions/checkout@v3
21
+ uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
14
22
 
15
23
  - name: Check PR title
16
- uses: rudderlabs/github-action-check-pr-title@v1.0.7
24
+ uses: rudderlabs/github-action-check-pr-title@29308e12984bdb3f3dcf8dcec3308f4f4652529c # v1.0.7
@@ -0,0 +1,64 @@
1
+ name: Publish gem
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: publish-gem-${{ github.event.release.tag_name }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ publish:
16
+ name: Publish to RubyGems
17
+ if: ${{ !github.event.release.prerelease }}
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 10
20
+ environment: release
21
+ env:
22
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
23
+ permissions:
24
+ contents: read
25
+ id-token: write # Required for RubyGems OpenID Connect trusted publishing.
26
+
27
+ steps:
28
+ - name: Harden the runner (Audit all outbound calls)
29
+ uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
30
+ with:
31
+ egress-policy: audit
32
+
33
+ - name: Check out the release tag
34
+ uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
35
+ with:
36
+ ref: ${{ github.event.release.tag_name }}
37
+ persist-credentials: false
38
+
39
+ - name: Set up Ruby
40
+ uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
41
+ with:
42
+ ruby-version: '2.7'
43
+
44
+ - name: Validate the release version
45
+ id: release
46
+ run: |
47
+ version="$(./scripts/publish_gem.sh validate)"
48
+ echo "version=$version" >> "$GITHUB_OUTPUT"
49
+
50
+ - name: Build the gem
51
+ env:
52
+ GEM_FILE: rudder_analytics_sync-${{ steps.release.outputs.version }}.gem
53
+ run: gem build rudder_analytics_sync.gemspec --output "$GEM_FILE"
54
+
55
+ - name: Configure RubyGems trusted publishing
56
+ uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
57
+
58
+ - name: Publish the gem
59
+ env:
60
+ GEM_FILE: rudder_analytics_sync-${{ steps.release.outputs.version }}.gem
61
+ run: gem push "$GEM_FILE"
62
+
63
+ - name: Verify the published version
64
+ run: ./scripts/publish_gem.sh verify
@@ -0,0 +1,45 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: release-please-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ release-please:
17
+ name: Create or update release PR
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - name: Harden the runner (Audit all outbound calls)
23
+ uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
24
+ with:
25
+ egress-policy: audit
26
+
27
+ # Mint a short-lived GitHub App installation token per run. Release
28
+ # Please needs a token other than GITHUB_TOKEN so its PR runs CI and its
29
+ # GitHub release triggers the publishing and notification workflows.
30
+ - name: Generate GitHub App token
31
+ id: generate-token
32
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
33
+ with:
34
+ client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
35
+ private-key: ${{ secrets.RELEASE_PRIVATE_KEY }}
36
+ permission-contents: write
37
+ permission-pull-requests: write
38
+
39
+ - name: Run release-please
40
+ uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
41
+ with:
42
+ token: ${{ steps.generate-token.outputs.token }}
43
+ target-branch: master
44
+ config-file: release-please-config.json
45
+ manifest-file: .release-please-manifest.json
@@ -4,14 +4,22 @@ on:
4
4
  release:
5
5
  types: [created]
6
6
 
7
+ permissions:
8
+ contents: read
9
+
7
10
  jobs:
8
11
  deploy-tag:
9
12
  name: Notify Slack
10
13
  runs-on: ubuntu-latest
11
14
  steps:
15
+ - name: Harden the runner (Audit all outbound calls)
16
+ uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
17
+ with:
18
+ egress-policy: audit
19
+
12
20
  - name: Send message to Slack channel
13
21
  id: slack
14
- uses: slackapi/slack-github-action@v1.23.0
22
+ uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844 # v1.23.0
15
23
  env:
16
24
  SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
17
25
  PROJECT_NAME: 'Ruby(Sync) SDK'
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.1.0"
3
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ ## [2.1.0](https://github.com/rudderlabs/rudder-sdk-ruby-sync/compare/v2.0.1...v2.1.0) (2026-09-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * sdk-5298 add bounded ruby sync retries ([#29](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/29)) ([6ce86dc](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/6ce86dc308f985042fe62dc67b0bf620c5c3827b))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **vuln:** pin and bump action refs (SEC-171) ([#24](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/24)) ([b87c6b8](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/b87c6b8b9a197325d7446d963b21640ed5b006ab))
14
+
15
+
16
+ ### Miscellaneous
17
+
18
+ * add DeepWiki badge to README.md ([#23](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/23)) ([6ad71ca](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/6ad71ca9d3c19b45c3588fb0ed867e23ee8d8159))
19
+ * add issue template for bug report ([98a925e](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/98a925eb9f7f5ca1fa2cb05b86443b6a10c572fe))
20
+ * add issue template for bug report ([1d4071d](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/1d4071dd24ae3407643d68504486688058ff0d79))
21
+ * addressing comments ([1926d1b](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/1926d1b7c9704676ceb456b46e7ad3652adeaf3a))
22
+ * apply security best practices from step security ([#16](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/16)) ([057c9ef](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/057c9ef9d5493a0cd6b1c42b6050de314cbf4e10))
23
+ * apply security best practices from step security ([#17](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/17)) ([a3df23c](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/a3df23c677ec886e3fdd44c206b07bf388ea7534))
24
+ * **ci:** refresh CI credentials (SEC-343) ([#27](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/27)) ([9142699](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/91426994d3af21c5fb1f0c45d84f4def156cbb3c))
25
+ * readme updates ([e12204b](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/e12204b694b4bed6d2b19e1ce40c73b5d424257d))
26
+ * remove notion-pr-sync workflow ([9d7d8d7](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/9d7d8d73718cd5fda06ff8f601046e858b0e8bf5))
27
+ * sdk-5400 automate ruby sync releases ([#31](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/31)) ([725830c](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/725830c31f74af0615b1b2318b8d648f8288dd92))
28
+ * sdk-5400 make batch spec version independent ([#33](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/33)) ([3b7d996](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/3b7d996bf8f0da912da3111c494ed86c6e906d2e))
29
+ * **vuln:** scope workflow permissions to least privilege (SEC-167) ([#26](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/26)) ([f2249da](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/f2249dadb8f569a70ff8e6bb0a053d52658c12be))
30
+ * **vuln:** zizmor --fix=all findings (SEC-199) ([#25](https://github.com/rudderlabs/rudder-sdk-ruby-sync/issues/25)) ([b2fbde4](https://github.com/rudderlabs/rudder-sdk-ruby-sync/commit/b2fbde4b2df38defffe9a50e62065efc446fa9af))
data/LICENSE.txt CHANGED
@@ -19,3 +19,25 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
22
+
23
+ MIT License
24
+
25
+ Copyright (c) 2020 RudderStack.
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
data/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <b>
11
11
  <a href="https://rudderstack.com">Website</a>
12
12
  ·
13
- <a href="https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk/">Documentation</a>
13
+ <a href="https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk-sync/">Documentation</a>
14
14
  ·
15
15
  <a href="https://rudderstack.com/join-rudderstack-slack-community">Community Slack</a>
16
16
  </b>
@@ -18,6 +18,8 @@
18
18
 
19
19
  <p align="center"><a href="https://rubygems.org/gems/rudder_analytics_sync/"><img src="https://img.shields.io/gem/v/rudder_analytics_sync?style=flat"/></a></p>
20
20
 
21
+ <p align="center"><a href="https://deepwiki.com/rudderlabs/rudder-sdk-ruby-sync"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"/></a></p>
22
+
21
23
  ----
22
24
 
23
25
  # RudderStack Ruby SDK
@@ -28,7 +30,7 @@ The RudderStack Ruby SDK lets you send customer event data from your Ruby applic
28
30
 
29
31
  - Set up a [RudderStack open source](https://app.rudderstack.com/signup?type=opensource) account.
30
32
  - Set up a Ruby source in the dashboard.
31
- - Copy the write key and the data plane URL. For more information, refer to the [Ruby SDK documentation](https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk/#sdk-setup-requirements).
33
+ - Copy the write key and the data plane URL. For more information, refer to the [Ruby SDK documentation](https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk-sync/#sdk-setup-requirements).
32
34
 
33
35
  ## Installation
34
36
 
@@ -69,6 +71,28 @@ analytics.track(
69
71
  )
70
72
  ```
71
73
 
74
+ ## Retry behavior
75
+
76
+ Retries are disabled by default to preserve existing synchronous request behavior. You can enable a limited retry budget for HTTP `429`, HTTP `5xx`, and temporary network failures. When enabled, the SDK uses bounded exponential backoff with jitter and honors numeric and HTTP-date `Retry-After` headers.
77
+
78
+ Retries block the calling thread until delivery succeeds or the retry budget is exhausted. A retry after an ambiguous network failure can also deliver the same event more than once.
79
+
80
+ Configure retry behavior when you create the client:
81
+
82
+ ```ruby
83
+ analytics = RudderAnalyticsSync::Client.new(
84
+ write_key: 'WRITE_KEY',
85
+ retry_enabled: true,
86
+ max_retries: 3, # Set to 0 to disable retries
87
+ retry_base_delay: 0.1, # Seconds
88
+ max_retry_delay: 30, # Seconds, including Retry-After
89
+ retry_jitter_ratio: 0.2,
90
+ respect_retry_after: true
91
+ )
92
+ ```
93
+
94
+ `retries` is also accepted as a total-attempt count. For example, `retries: 4` permits one initial attempt and three retries.
95
+
72
96
  ## Gzip support
73
97
 
74
98
  From version 2.0.0, the Ruby SDK supports Gzip compression and it is enabled (set to `true`) by default. However, you can disable this feature by setting the `Gzip` parameter to `false` while initializing the SDK, as shown:
@@ -90,7 +114,11 @@ analytics = RudderAnalyticsSync::Client.new(
90
114
 
91
115
  ## Sending events
92
116
 
93
- Refer to the [RudderStack Ruby SDK documentation](https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk/) for more information on the supported event types.
117
+ Refer to the [RudderStack Ruby SDK documentation](https://www.rudderstack.com/docs/sources/event-streams/sdks/rudderstack-ruby-sdk-sync/) for more information on the supported event types.
118
+
119
+ ## Releasing
120
+
121
+ Maintainers must use the automated release process in [RELEASING.md](RELEASING.md).
94
122
 
95
123
  | From version 2.0.0, the Ruby SDK supports [`screen`](https://www.rudderstack.com/docs/event-spec/standard-events/screen/) events. |
96
124
  | :-----|
data/RELEASING.md ADDED
@@ -0,0 +1,29 @@
1
+ # Release process
2
+
3
+ Release Please calculates the next version from Conventional Commit messages on `master`. It updates one release pull request with the version and changelog.
4
+
5
+ The first automated release uses `v2.0.1` as its baseline. A `feat` commit after that tag produces version `2.1.0`. A `fix` commit produces a patch version. A commit with a `BREAKING CHANGE` footer produces a major version.
6
+
7
+ ## One-time configuration
8
+
9
+ 1. Give this repository access to the organization `RELEASE_APP_CLIENT_ID` variable and `RELEASE_PRIVATE_KEY` secret.
10
+ 2. Create the GitHub `release` environment.
11
+ 3. Limit the environment deployment policy to tags that match `v*`.
12
+ 4. Add a RubyGems trusted publisher for `rudder_analytics_sync` with these values:
13
+ - GitHub organization: `rudderlabs`
14
+ - GitHub repository: `rudder-sdk-ruby-sync`
15
+ - Workflow filename: `publish.yml`
16
+ - Environment: `release`
17
+
18
+ ## Create a release
19
+
20
+ 1. Merge changes to `master` with Conventional Commit pull request titles.
21
+ 2. Wait for Release Please to create or update the release pull request.
22
+ 3. Review the proposed version and `CHANGELOG.md` changes.
23
+ 4. Merge the release pull request.
24
+ 5. Confirm that GitHub creates the release and the `Publish gem` workflow succeeds.
25
+ 6. Confirm that RubyGems shows the same version for `rudder_analytics_sync`.
26
+
27
+ The publish workflow checks out the release tag. It then confirms that the tag, source version, and gemspec version match. RubyGems uses GitHub OpenID Connect trusted publishing, so the workflow does not store a long-lived RubyGems API key.
28
+
29
+ If publication fails, fix the cause and rerun the failed workflow. Do not create a replacement tag for the same version.
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rudder_analytics_sync/logging'
4
+ require 'rudder_analytics_sync/retry'
4
5
 
5
6
  module RudderAnalyticsSync
6
7
  class Configuration
7
8
  include RudderAnalyticsSync::Utils
8
9
  include RudderAnalyticsSync::Logging
9
- attr_reader :write_key, :data_plane_url, :on_error, :stub, :logger, :http_options, :gzip
10
+ attr_reader :write_key, :data_plane_url, :on_error, :stub, :logger, :http_options, :gzip,
11
+ :retry_enabled, :max_retries, :retry_base_delay, :max_retry_delay,
12
+ :retry_jitter_ratio, :respect_retry_after
10
13
 
11
14
  def initialize(settings = {})
12
15
  symbolized_settings = symbolize_keys(settings)
@@ -18,8 +21,35 @@ module RudderAnalyticsSync
18
21
  @http_options = { use_ssl: true }
19
22
  .merge(symbolized_settings[:http_options] || {})
20
23
  @gzip = symbolized_settings[:gzip]
24
+ configure_retry(symbolized_settings)
21
25
  raise ArgumentError, 'Missing required option :write_key' \
22
26
  unless @write_key
23
27
  end
28
+
29
+ private
30
+
31
+ def configure_retry(settings)
32
+ @retry_enabled = settings.key?(:retry_enabled) ? settings[:retry_enabled] : false
33
+ @max_retries = normalize_max_retries(settings)
34
+ @retry_base_delay = non_negative_number(settings.fetch(:retry_base_delay, Retry::DEFAULT_BASE_DELAY))
35
+ @max_retry_delay = non_negative_number(settings.fetch(:max_retry_delay, Retry::DEFAULT_MAX_DELAY))
36
+ @retry_jitter_ratio = [[settings.fetch(:retry_jitter_ratio, Retry::DEFAULT_JITTER_RATIO).to_f, 0].max, 1].min
37
+ @respect_retry_after = settings.key?(:respect_retry_after) ? settings[:respect_retry_after] : true
38
+ end
39
+
40
+ def normalize_max_retries(settings)
41
+ value = if settings.key?(:max_retries)
42
+ settings[:max_retries]
43
+ elsif settings.key?(:retries)
44
+ settings[:retries].to_i - 1
45
+ else
46
+ Retry::DEFAULT_MAX_RETRIES
47
+ end
48
+ [value.to_i, 0].max
49
+ end
50
+
51
+ def non_negative_number(value)
52
+ [value.to_f, 0].max
53
+ end
24
54
  end
25
55
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'rudder_analytics_sync/constants'
4
4
  require 'rudder_analytics_sync/logging'
5
+ require 'rudder_analytics_sync/retry'
5
6
  require('zlib')
6
7
 
7
8
  module RudderAnalyticsSync
@@ -19,44 +20,111 @@ module RudderAnalyticsSync
19
20
  @logger = client.config.logger
20
21
  @http_options = client.config.http_options
21
22
  @gzip = client.config.gzip.nil? ? true : client.config.gzip
23
+ @retry = Retry.new(client.config)
22
24
  end
23
25
 
24
- def post(path, payload, headers: DEFAULT_HEADERS) # rubocop:disable Metrics/AbcSize
25
- response = nil
26
- status_code = nil
27
- response_body = nil
28
-
29
- uri = URI(data_plane_url)
30
- if stub
31
- logger.debug "stubbed request to \
32
- #{path}: write key = #{write_key}, \
33
- payload = #{JSON.generate(payload)}"
34
-
35
- { status: 200, error: nil }
36
- else
37
- if gzip
38
- headers = headers.merge(
39
- 'Content-Encoding': 'gzip'
40
- )
41
- gzip = Zlib::GzipWriter.new(StringIO.new)
42
- gzip << payload.to_json
43
- payload = gzip.close.string
44
- else
45
- payload = JSON.generate(payload)
46
- end
47
- Net::HTTP.start(uri.host, uri.port, :ENV, http_options) do |http|
48
- request = Net::HTTP::Post.new(path, headers)
49
- request.basic_auth write_key, nil
50
- http.request(request, payload).tap do |res|
51
- status_code = res.code
52
- response_body = res.body
53
- response = res
54
- response.value
26
+ def post(path, payload, headers: DEFAULT_HEADERS)
27
+ begin
28
+ uri = URI(data_plane_url)
29
+ return stub_post(path, payload) if stub
30
+
31
+ payload, headers = encode(payload, headers)
32
+ rescue StandardError => e
33
+ return error_handler.call(nil, nil, e, nil)
34
+ end
35
+
36
+ execute_post(uri, path, payload, headers)
37
+ end
38
+
39
+ private
40
+
41
+ def stub_post(path, payload)
42
+ logger.debug "stubbed request to \
43
+ #{path}: write key = #{write_key}, \
44
+ payload = #{JSON.generate(payload)}"
45
+
46
+ { status: 200, error: nil }
47
+ end
48
+
49
+ def encode(payload, headers)
50
+ return [JSON.generate(payload), headers] unless gzip
51
+
52
+ headers = headers.merge('Content-Encoding': 'gzip')
53
+ writer = Zlib::GzipWriter.new(StringIO.new)
54
+ writer << payload.to_json
55
+ [writer.close.string, headers]
56
+ end
57
+
58
+ def execute_post(uri, path, payload, headers) # rubocop:disable Metrics/MethodLength
59
+ completed_retries = 0
60
+ loop do
61
+ response = nil
62
+ begin
63
+ response = perform_post(uri, path, payload, headers)
64
+ return response if success_status?(response.code)
65
+
66
+ if retry_status?(response.code, completed_retries)
67
+ completed_retries = retry_response(completed_retries, response)
68
+ next
55
69
  end
70
+
71
+ response.value
72
+ rescue StandardError => e
73
+ if response.nil? && retry_error?(e, completed_retries)
74
+ completed_retries = retry_exception(completed_retries, e)
75
+ next
76
+ end
77
+
78
+ return report_error(e, response)
56
79
  end
57
80
  end
58
- rescue StandardError => e
59
- error_handler.call(status_code, response_body, e, response)
81
+ end
82
+
83
+ def perform_post(uri, path, payload, headers)
84
+ Net::HTTP.start(uri.host, uri.port, :ENV, http_options) do |http|
85
+ request = Net::HTTP::Post.new(path, headers)
86
+ request.basic_auth write_key, nil
87
+ http.request(request, payload)
88
+ end
89
+ end
90
+
91
+ def success_status?(status_code)
92
+ status_code.to_i >= 200 && status_code.to_i < 300
93
+ end
94
+
95
+ def retry_status?(status_code, completed_retries)
96
+ @retry.enabled? && @retry.retryable_status?(status_code) && @retry.retries_remaining?(completed_retries)
97
+ end
98
+
99
+ def retry_error?(error, completed_retries)
100
+ @retry.enabled? && @retry.retryable_error?(error) && @retry.retries_remaining?(completed_retries)
101
+ end
102
+
103
+ def retry_response(completed_retries, response)
104
+ retry_number = completed_retries + 1
105
+ wait_before_retry(retry_number, response, "HTTP #{response.code}")
106
+ retry_number
107
+ end
108
+
109
+ def retry_exception(completed_retries, error)
110
+ retry_number = completed_retries + 1
111
+ wait_before_retry(retry_number, nil, error.class.name)
112
+ retry_number
113
+ end
114
+
115
+ def report_error(error, response)
116
+ error_handler.call(response&.code, response&.body, error, response)
117
+ end
118
+
119
+ def wait_before_retry(retry_number, response, reason)
120
+ delay = @retry.delay(retry_number, response)
121
+ logger.debug "Retrying request after #{reason} in #{delay}s " \
122
+ "(retry #{retry_number} of #{@retry.max_retries})"
123
+ sleep_retry_delay(delay)
124
+ end
125
+
126
+ def sleep_retry_delay(delay)
127
+ sleep(delay) if delay.positive?
60
128
  end
61
129
  end
62
130
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'time'
6
+ require 'timeout'
7
+
8
+ module RudderAnalyticsSync
9
+ class Retry
10
+ DEFAULT_MAX_RETRIES = 3
11
+ DEFAULT_BASE_DELAY = 0.1
12
+ DEFAULT_MAX_DELAY = 30.0
13
+ DEFAULT_JITTER_RATIO = 0.2
14
+
15
+ RETRYABLE_ERRORS = [
16
+ Timeout::Error,
17
+ EOFError,
18
+ IOError,
19
+ SocketError,
20
+ Errno::ECONNREFUSED,
21
+ Errno::ECONNRESET,
22
+ Errno::EHOSTUNREACH,
23
+ Errno::ENETUNREACH,
24
+ Errno::ETIMEDOUT,
25
+ Net::OpenTimeout,
26
+ Net::ReadTimeout,
27
+ Net::ProtocolError,
28
+ OpenSSL::SSL::SSLError
29
+ ].freeze
30
+
31
+ def initialize(config, clock: proc { Time.now }, random: proc { rand })
32
+ @config = config
33
+ @clock = clock
34
+ @random = random
35
+ end
36
+
37
+ def enabled?
38
+ @config.retry_enabled && @config.max_retries.positive?
39
+ end
40
+
41
+ def max_retries
42
+ @config.max_retries
43
+ end
44
+
45
+ def retries_remaining?(completed_retries)
46
+ completed_retries < @config.max_retries
47
+ end
48
+
49
+ def retryable_status?(status_code)
50
+ status_code = status_code.to_i
51
+ status_code == 429 || (status_code >= 500 && status_code <= 599)
52
+ end
53
+
54
+ def retryable_error?(error)
55
+ RETRYABLE_ERRORS.any? { |error_class| error.is_a?(error_class) }
56
+ end
57
+
58
+ def delay(retry_number, response = nil)
59
+ backoff = @config.retry_base_delay * (2**(retry_number - 1))
60
+ retry_after = retry_after_delay(response)
61
+ base = [[backoff, retry_after].max, @config.max_retry_delay].min
62
+ jittered = base + (base * @config.retry_jitter_ratio * @random.call)
63
+ [jittered, @config.max_retry_delay].min
64
+ end
65
+
66
+ def parse_retry_after(value)
67
+ value = value.to_s.strip
68
+ return value.to_i if value.match?(/\A\d+\z/)
69
+
70
+ [Time.httpdate(value) - @clock.call, 0].max
71
+ rescue ArgumentError
72
+ nil
73
+ end
74
+
75
+ private
76
+
77
+ def retry_after_delay(response)
78
+ return 0 unless @config.respect_retry_after && response
79
+
80
+ parse_retry_after(response['Retry-After']) || 0
81
+ end
82
+ end
83
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RudderAnalyticsSync
4
- VERSION = '2.0.1'
4
+ VERSION = '2.1.0'
5
5
  end
@@ -4,6 +4,7 @@ require 'net/http'
4
4
  require 'json'
5
5
  require 'time'
6
6
  require 'rudder_analytics_sync/version'
7
+ require 'rudder_analytics_sync/retry'
7
8
  require 'rudder_analytics_sync/client'
8
9
 
9
10
  module RudderAnalyticsSync
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
3
+ "release-type": "ruby",
4
+ "include-component-in-tag": false,
5
+ "pull-request-title-pattern": "chore: release ${version}",
6
+ "always-update": true,
7
+ "changelog-sections": [
8
+ { "type": "feat", "section": "Features", "hidden": false },
9
+ { "type": "fix", "section": "Bug Fixes", "hidden": false },
10
+ { "type": "perf", "section": "Performance Improvements", "hidden": false },
11
+ { "type": "deps", "section": "Dependencies", "hidden": false },
12
+ { "type": "revert", "section": "Reverts", "hidden": false },
13
+ { "type": "chore", "section": "Miscellaneous", "hidden": false },
14
+ { "type": "ci", "section": "Miscellaneous", "hidden": false },
15
+ { "type": "build", "section": "Miscellaneous", "hidden": false },
16
+ { "type": "refactor", "section": "Miscellaneous", "hidden": false },
17
+ { "type": "test", "section": "Miscellaneous", "hidden": false },
18
+ { "type": "docs", "section": "Documentation", "hidden": false }
19
+ ],
20
+ "packages": {
21
+ ".": {
22
+ "package-name": "rudder_analytics_sync",
23
+ "changelog-path": "CHANGELOG.md",
24
+ "version-file": "lib/rudder_analytics_sync/version.rb"
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ readonly GEM_NAME='rudder_analytics_sync'
6
+ readonly GEMSPEC_PATH='rudder_analytics_sync.gemspec'
7
+ readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ readonly REPOSITORY_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+ readonly RUBYGEMS_API_BASE_URL="${RUBYGEMS_API_BASE_URL:-https://rubygems.org}"
10
+ readonly VERIFY_RETRY_COUNT=18
11
+ readonly VERIFY_RETRY_DELAY_SECONDS=5
12
+
13
+ usage() {
14
+ echo "Usage: RELEASE_TAG=v<major>.<minor>.<patch> $0 <validate|verify>" >&2
15
+ exit 1
16
+ }
17
+
18
+ validate_release() {
19
+ if [[ ! "${RELEASE_TAG:-}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
20
+ echo "Expected RELEASE_TAG in the form v<major>.<minor>.<patch>, got: ${RELEASE_TAG:-<unset>}" >&2
21
+ exit 1
22
+ fi
23
+
24
+ local tag_version="${RELEASE_TAG#v}"
25
+ local source_version
26
+ local gemspec_version
27
+ source_version="$(ruby -Ilib -rrudder_analytics_sync/version -e 'print RudderAnalyticsSync::VERSION')"
28
+ gemspec_version="$(ruby -Ilib -e 'spec = Gem::Specification.load("rudder_analytics_sync.gemspec") or abort "Unable to load gemspec"; print spec.version')"
29
+
30
+ if [[ "$tag_version" != "$source_version" ]]; then
31
+ echo "Release tag version ($tag_version) does not match source version ($source_version)." >&2
32
+ exit 1
33
+ fi
34
+
35
+ if [[ "$source_version" != "$gemspec_version" ]]; then
36
+ echo "Source version ($source_version) does not match gemspec version ($gemspec_version)." >&2
37
+ exit 1
38
+ fi
39
+
40
+ echo "$source_version"
41
+ }
42
+
43
+ verify_published_version() {
44
+ local gem_version="$1"
45
+ local published_version
46
+ local response
47
+ response="$(
48
+ curl \
49
+ --fail \
50
+ --retry "$VERIFY_RETRY_COUNT" \
51
+ --retry-all-errors \
52
+ --retry-delay "$VERIFY_RETRY_DELAY_SECONDS" \
53
+ --silent \
54
+ --show-error \
55
+ --header 'Cache-Control: no-cache' \
56
+ "$RUBYGEMS_API_BASE_URL/api/v2/rubygems/$GEM_NAME/versions/$gem_version.json"
57
+ )"
58
+ published_version="$(
59
+ ruby -rjson -e 'print JSON.parse(STDIN.read).fetch("version")' <<< "$response"
60
+ )"
61
+
62
+ if [[ "$published_version" != "$gem_version" ]]; then
63
+ echo "RubyGems returned version $published_version; expected $gem_version." >&2
64
+ exit 1
65
+ fi
66
+ }
67
+
68
+ main() {
69
+ if [[ $# -ne 1 ]]; then
70
+ usage
71
+ fi
72
+
73
+ cd "$REPOSITORY_ROOT"
74
+
75
+ case "$1" in
76
+ validate)
77
+ validate_release
78
+ ;;
79
+ verify)
80
+ version="$(validate_release)"
81
+ verify_published_version "$version"
82
+ ;;
83
+ *)
84
+ usage
85
+ ;;
86
+ esac
87
+ }
88
+
89
+ main "$@"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rudder_analytics_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RudderStack
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-30 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -118,20 +118,25 @@ executables: []
118
118
  extensions: []
119
119
  extra_rdoc_files: []
120
120
  files:
121
+ - ".github/ISSUE_TEMPLATE/bug_report.md"
121
122
  - ".github/dependabot.yml"
122
123
  - ".github/pull_request_template.md"
123
124
  - ".github/workflows/build-and-quality-checks.yml"
124
125
  - ".github/workflows/check-pr-title.yml"
125
- - ".github/workflows/notion-pr-sync.yml"
126
+ - ".github/workflows/publish.yml"
127
+ - ".github/workflows/release-please.yml"
126
128
  - ".github/workflows/slack-notify.yml"
127
129
  - ".gitignore"
128
130
  - ".hound.yml"
131
+ - ".release-please-manifest.json"
129
132
  - ".rspec"
130
133
  - ".rubocop.yml"
134
+ - CHANGELOG.md
131
135
  - CODEOWNERS
132
136
  - Gemfile
133
137
  - LICENSE.txt
134
138
  - README.md
139
+ - RELEASING.md
135
140
  - Rakefile
136
141
  - SECURITY.md
137
142
  - bin/console
@@ -152,15 +157,18 @@ files:
152
157
  - lib/rudder_analytics_sync/operations/screen.rb
153
158
  - lib/rudder_analytics_sync/operations/track.rb
154
159
  - lib/rudder_analytics_sync/request.rb
160
+ - lib/rudder_analytics_sync/retry.rb
155
161
  - lib/rudder_analytics_sync/utils.rb
156
162
  - lib/rudder_analytics_sync/version.rb
163
+ - release-please-config.json
157
164
  - rudder_analytics_sync.gemspec
165
+ - scripts/publish_gem.sh
158
166
  homepage: https://github.com/rudderlabs/rudder-sdk-ruby-sync
159
167
  licenses:
160
168
  - MIT
161
169
  metadata:
162
170
  rubygems_mfa_required: 'true'
163
- post_install_message:
171
+ post_install_message:
164
172
  rdoc_options: []
165
173
  require_paths:
166
174
  - lib
@@ -175,8 +183,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
183
  - !ruby/object:Gem::Version
176
184
  version: '0'
177
185
  requirements: []
178
- rubygems_version: 3.0.3.1
179
- signing_key:
186
+ rubygems_version: 3.1.6
187
+ signing_key:
180
188
  specification_version: 4
181
189
  summary: Privacy and Security focused Segment-alternative. Ruby SDK (sync)
182
190
  test_files: []
@@ -1,55 +0,0 @@
1
- name: Notion PR Sync
2
-
3
- on:
4
- issues:
5
- types:
6
- [
7
- opened,
8
- edited,
9
- deleted,
10
- transferred,
11
- pinned,
12
- unpinned,
13
- closed,
14
- reopened,
15
- assigned,
16
- unassigned,
17
- labeled,
18
- unlabeled,
19
- locked,
20
- unlocked,
21
- milestoned,
22
- demilestoned,
23
- ]
24
- pull_request:
25
- types:
26
- [
27
- assigned,
28
- unassigned,
29
- labeled,
30
- unlabeled,
31
- opened,
32
- edited,
33
- closed,
34
- reopened,
35
- synchronize,
36
- converted_to_draft,
37
- ready_for_review,
38
- locked,
39
- unlocked,
40
- review_requested,
41
- review_request_removed,
42
- auto_merge_enabled,
43
- auto_merge_disabled,
44
- ]
45
-
46
- jobs:
47
- request:
48
- runs-on: ubuntu-latest
49
- steps:
50
- - name: Sync Github PRs to Notion
51
- uses: sivashanmukh/github-notion-pr-sync@1.0.0
52
- with:
53
- notionKey: ${{ secrets.NOTION_BOT_KEY }}
54
- notionDatabaseId: ${{ secrets.NOTION_PR_DB_ID }}
55
- githubKey: ${{ secrets.PAT }}