Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions sentry-api-client/Public/Get-SentryMetrics.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
function Get-SentryMetrics {
<#
.SYNOPSIS
Retrieves metrics from Sentry.

.DESCRIPTION
Fetches Sentry metrics matching specified criteria.
Supports filtering by query, time range, and custom fields.
Uses the Sentry Discover API with the 'tracemetrics' dataset.

.PARAMETER Query
Search query string using Sentry search syntax (e.g., 'metric.name:my.counter', 'metric.type:counter').

.PARAMETER StatsPeriod
Relative time period (e.g., '24h', '7d', '14d'). Default is '24h'.

.PARAMETER Limit
Maximum number of metrics to return. Default is 100.

.PARAMETER Cursor
Pagination cursor for retrieving subsequent pages of results.

.PARAMETER Fields
Specific fields to return. Default includes: id, metric.name, metric.type, value, timestamp.

.EXAMPLE
Get-SentryMetrics -Query 'metric.name:my.counter'

.EXAMPLE
Get-SentryMetrics -Query 'metric.name:my.counter test_id:abc123' -StatsPeriod '7d'
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[string]$Query,

[Parameter(Mandatory = $false)]
[string]$StatsPeriod = '24h',

[Parameter(Mandatory = $false)]
[int]$Limit = 100,

[Parameter(Mandatory = $false)]
[string]$Cursor,

[Parameter(Mandatory = $false)]
[string[]]$Fields
)

# Default fields for metrics if not specified
if (-not $Fields -or $Fields.Count -eq 0) {
$Fields = @(
'id',
'metric.name',
'metric.type',
'value',
'timestamp'
)
}

$QueryParams = @{
dataset = 'tracemetrics'
statsPeriod = $StatsPeriod
per_page = $Limit
field = $Fields
}

if ($Query) {
$QueryParams.query = $Query
}

if ($Cursor) {
$QueryParams.cursor = $Cursor
}

$QueryString = Build-QueryString -Parameters $QueryParams
$Uri = Get-SentryOrganizationUrl -Resource "events/" -QueryString $QueryString

try {
$Response = Invoke-SentryApiRequest -Uri $Uri -Method 'GET'
return $Response
}
catch {
Write-Error "Failed to retrieve metrics - $_"
throw
}
}
76 changes: 76 additions & 0 deletions sentry-api-client/Public/Get-SentryMetricsByAttribute.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function Get-SentryMetricsByAttribute {
<#
.SYNOPSIS
Retrieves metrics filtered by metric name and a specific attribute.

.DESCRIPTION
Fetches Sentry metrics that match a specific metric name and attribute
name/value pair. This is a convenience wrapper around Get-SentryMetrics
for common use cases like filtering by test_id for integration testing.

.PARAMETER MetricName
The name of the metric to filter by (e.g., 'test.integration.counter').

.PARAMETER AttributeName
The name of the attribute to filter by (e.g., 'test_id').

.PARAMETER AttributeValue
The value of the attribute to match.

.PARAMETER Limit
Maximum number of metrics to return. Default is 100.

.PARAMETER StatsPeriod
Relative time period (e.g., '24h', '7d'). Default is '24h'.

.PARAMETER Fields
Additional fields to include in the response. These are merged with default fields
(id, metric.name, metric.type, value, timestamp) and the filter attribute.

.EXAMPLE
Get-SentryMetricsByAttribute -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue 'abc-123'

.EXAMPLE
Get-SentryMetricsByAttribute -MetricName 'my.counter' -AttributeName 'user_id' -AttributeValue '12345' -StatsPeriod '7d'
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$MetricName,

[Parameter(Mandatory = $true)]
[string]$AttributeName,

[Parameter(Mandatory = $true)]
[string]$AttributeValue,

[Parameter(Mandatory = $false)]
[int]$Limit = 100,

[Parameter(Mandatory = $false)]
[string]$StatsPeriod = '24h',

[Parameter(Mandatory = $false)]
[string[]]$Fields
)

$Query = "metric.name:$MetricName $AttributeName`:$AttributeValue"

# Include default fields plus the attribute we're filtering by
$DefaultFields = @(
'id',
'metric.name',
'metric.type',
'value',
'timestamp',
$AttributeName
)

if ($Fields) {
$AllFields = @($DefaultFields + $Fields) | Select-Object -Unique
} else {
$AllFields = $DefaultFields
}

return Get-SentryMetrics -Query $Query -Limit $Limit -StatsPeriod $StatsPeriod -Fields $AllFields
}
2 changes: 2 additions & 0 deletions sentry-api-client/SentryApiClient.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
'Get-SentryEventsByTag',
'Get-SentryLogs',
'Get-SentryLogsByAttribute',
'Get-SentryMetrics',
'Get-SentryMetricsByAttribute',
'Invoke-SentryCLI'
)

Expand Down
40 changes: 40 additions & 0 deletions sentry-api-client/Tests/Fixtures/SentryMetricsResponses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"metrics_list": {
"data": [
{
"id": "019bbce4569e7f5f81466757e8f84001",
"metric.name": "test.integration.counter",
"metric.type": "counter",
"value": 42.0,
"timestamp": "2025-01-14T14:32:45+00:00",
"test_id": "metrics-test-001"
},
{
"id": "019bbce4569e7f5f81466757e8f84002",
"metric.name": "test.integration.counter",
"metric.type": "counter",
"value": 7.0,
"timestamp": "2025-01-14T14:32:46+00:00",
"test_id": "metrics-test-001"
}
],
"meta": {
"fields": {
"id": "string",
"metric.name": "string",
"metric.type": "string",
"value": "number",
"timestamp": "date",
"test_id": "string"
},
"units": {}
}
},
"metrics_empty": {
"data": [],
"meta": {
"fields": {},
"units": {}
}
}
}
138 changes: 138 additions & 0 deletions sentry-api-client/Tests/SentryApiClient.Metrics.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
BeforeAll {
$ModulePath = Join-Path $PSScriptRoot '..' 'SentryApiClient.psd1'
Import-Module $ModulePath -Force

# Load test fixtures
$FixturesPath = Join-Path $PSScriptRoot 'Fixtures' 'SentryMetricsResponses.json'
$Script:MetricsFixtures = Get-Content $FixturesPath | ConvertFrom-Json -AsHashtable
}

AfterAll {
Remove-Module SentryApiClient -Force
}

Describe 'SentryApiClient Metrics Functions' {
Context 'Module Export' {
It 'Should export Get-SentryMetrics function' {
Get-Command Get-SentryMetrics -Module SentryApiClient | Should -Not -BeNullOrEmpty
}

It 'Should export Get-SentryMetricsByAttribute function' {
Get-Command Get-SentryMetricsByAttribute -Module SentryApiClient | Should -Not -BeNullOrEmpty
}
}

Context 'Get-SentryMetrics' {
BeforeAll {
Mock -ModuleName SentryApiClient Invoke-WebRequest {
return @{ Content = ($Script:MetricsFixtures.metrics_list | ConvertTo-Json -Depth 10) }
}

Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
}

It 'Should retrieve metrics from tracemetrics dataset' {
$result = Get-SentryMetrics -Query 'metric.name:test.integration.counter'

$result | Should -Not -BeNullOrEmpty
$result.data | Should -HaveCount 2

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'dataset=tracemetrics' -and
$Uri -match 'organizations/test-org/events/'
}
}

It 'Should include default fields when none specified' {
Get-SentryMetrics -Query 'metric.name:test.integration.counter'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=id' -and
$Uri -match 'field=metric\.name' -and
$Uri -match 'field=metric\.type' -and
$Uri -match 'field=value' -and
$Uri -match 'field=timestamp'
}
}

It 'Should use custom fields when specified' {
Get-SentryMetrics -Query 'metric.name:test.counter' -Fields @('id', 'value', 'custom_field')

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=id' -and
$Uri -match 'field=value' -and
$Uri -match 'field=custom_field'
}
}

It 'Should pass stats period parameter' {
Get-SentryMetrics -Query 'metric.name:test.counter' -StatsPeriod '7d'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'statsPeriod=7d'
}
}
}

Context 'Get-SentryMetricsByAttribute' {
BeforeAll {
Mock -ModuleName SentryApiClient Invoke-WebRequest {
return @{ Content = ($Script:MetricsFixtures.metrics_list | ConvertTo-Json -Depth 10) }
}

Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
}

It 'Should query by metric name and attribute' {
$result = Get-SentryMetricsByAttribute -MetricName 'test.integration.counter' -AttributeName 'test_id' -AttributeValue 'metrics-test-001'

$result | Should -Not -BeNullOrEmpty
Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'query=metric\.name%3Atest\.integration\.counter' -and
$Uri -match 'test_id%3Ametrics-test-001'
}
}

It 'Should include the filter attribute in response fields' {
Get-SentryMetricsByAttribute -MetricName 'test.counter' -AttributeName 'test_id' -AttributeValue 'abc-123'

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=test_id'
}
}

It 'Should merge additional fields with defaults' {
Get-SentryMetricsByAttribute -MetricName 'test.counter' -AttributeName 'test_id' -AttributeValue 'abc-123' -Fields @('extra_field')

Assert-MockCalled -ModuleName SentryApiClient Invoke-WebRequest -ParameterFilter {
$Uri -match 'field=extra_field' -and
$Uri -match 'field=metric\.name' -and
$Uri -match 'field=test_id'
}
}
}

Context 'Error Handling' {
BeforeAll {
Connect-SentryApi -ApiToken 'test-token' -Organization 'test-org' -Project 'test-project'
}

It 'Should handle API errors gracefully' {
Mock -ModuleName SentryApiClient Invoke-WebRequest {
throw [System.Net.WebException]::new('401 Unauthorized')
}

{ Get-SentryMetrics } | Should -Throw '*Sentry API request*failed*'
}
}

Context 'Connection Validation' {
BeforeEach {
Disconnect-SentryApi
}

It 'Should throw when organization is not configured' {
{ Get-SentryMetrics } | Should -Throw '*Organization not configured*'
}
}
}
Loading