Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: virtual fields to client #350

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions axiom/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type Client struct {
Notifiers *NotifiersService
Annotations *AnnotationsService
Tokens *TokensService
VirtualFields *VirtualFieldsService
}

// NewClient returns a new Axiom API client. It automatically takes its
Expand Down Expand Up @@ -136,6 +137,7 @@ func NewClient(options ...Option) (*Client, error) {
client.Notifiers = &NotifiersService{client: client, basePath: "/v2/notifiers"}
client.Annotations = &AnnotationsService{client: client, basePath: "/v2/annotations"}
client.Tokens = &TokensService{client: client, basePath: "/v2/tokens"}
client.VirtualFields = &VirtualFieldsService{client: client, basePath: "/v2/vfields"}

// Apply supplied options.
if err := client.Options(options...); err != nil {
Expand Down
3 changes: 3 additions & 0 deletions axiom/monitors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
BelowOrEqual // BelowOrEqual
Above // Above
AboveOrEqual // AboveOrEqual
AboveOrBelow // AboveOrBelow
)

func operatorFromString(s string) (c Operator, err error) {
Expand All @@ -40,6 +41,8 @@ func operatorFromString(s string) (c Operator, err error) {
c = Above
case AboveOrEqual.String():
c = AboveOrEqual
case AboveOrBelow.String():
c = AboveOrBelow
default:
err = fmt.Errorf("unknown operator %q", s)
}
Expand Down
5 changes: 3 additions & 2 deletions axiom/monitors_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions axiom/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ type OpsGenieConfig struct {
type PagerDutyConfig struct {
// RoutingKey is the routing key to use for authentication.
RoutingKey string `json:"routingKey,omitempty"`
// Token is the token required to access private resources in
// Pager Duty
Token string `json:"token,omitempty"`
}

type SlackConfig struct {
Expand Down
9 changes: 9 additions & 0 deletions axiom/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ type APIToken struct {
// OrganisationCapabilities is the organisation capabilities available to
// the token.
OrganisationCapabilities OrganisationCapabilities `json:"orgCapabilities"`
// SAMLAuthenticated is a flag that determines whether the token can access
// a SAML authenticated org
SAMLAuthenticated bool `json:"samlAuthenticated"`
}

// DatasetCapabilities represents the capabilities available to a token for a
Expand All @@ -107,6 +110,9 @@ type DatasetCapabilities struct {
// Data is the data capability and the actions that can be performed on
// them.
Data []Action `json:"data"`
// Share is the share capability and the actions that can be performed on
// them.
Share []Action `json:"share"`
}

// OrganisationCapabilities represents the capabilities available to a token for
Expand Down Expand Up @@ -154,6 +160,9 @@ type OrganisationCapabilities struct {
// Users is the Users capability and the actions that can be performed on
// them.
Users []Action `json:"users,omitempty"`
// Views is the view capability and the actions that can be performed on
// them.
Views []Action `json:"views,omitempty"`
}

// CreateTokenRequest is the request payload for creating a new token with the
Expand Down
127 changes: 127 additions & 0 deletions axiom/vfields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package axiom

import (
"context"
"net/http"
"net/url"

"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)

type VirtualField struct {
// Dataset is the dataset to which the virtual field belongs.
Dataset string `json:"dataset"`
// Name is the name of the virtual field.
Name string `json:"name"`
// Expression defines the virtual field's APL.
Expression string `json:"expression"`
// Description is an optional description of the virtual field.
Description string `json:"description,omitempty"`
// Type is the type of the virtual field. E.g. string | number
Type string `json:"type,omitempty"`
// Unit is the unit for the type of data returned by the virtual field.
Unit string `json:"unit,omitempty"`
}

type VirtualFieldWithID struct {
VirtualField
// ID is the unique identifier of the virtual field.
ID string `json:"id"`
}

// Axiom API Reference: /v2/vfields
type VirtualFieldsService service

// List all virtual fields for a given dataset.
func (s *VirtualFieldsService) List(ctx context.Context, dataset string) ([]*VirtualFieldWithID, error) {
ctx, span := s.client.trace(ctx, "VirtualFields.List", trace.WithAttributes(
attribute.String("axiom.param.dataset", dataset),
))
defer span.End()

params := url.Values{}
params.Set("dataset", dataset)

var res []*VirtualFieldWithID
if err := s.client.Call(ctx, http.MethodGet, s.basePath+"?"+params.Encode(), nil, &res); err != nil {
return nil, spanError(span, err)
}

return res, nil
}

// Get a virtual field by id.
func (s *VirtualFieldsService) Get(ctx context.Context, id string) (*VirtualFieldWithID, error) {
ctx, span := s.client.trace(ctx, "VirtualFields.Get", trace.WithAttributes(
attribute.String("axiom.virtual_field_id", id),
))
defer span.End()

path, err := url.JoinPath(s.basePath, id)
if err != nil {
return nil, spanError(span, err)
}

var res VirtualFieldWithID
if err := s.client.Call(ctx, http.MethodGet, path, nil, &res); err != nil {
return nil, spanError(span, err)
}

return &res, nil
}

// Create a virtual field with the given properties.
func (s *VirtualFieldsService) Create(ctx context.Context, req VirtualField) (*VirtualFieldWithID, error) {
ctx, span := s.client.trace(ctx, "VirtualFields.Create", trace.WithAttributes(
attribute.String("axiom.param.dataset", req.Dataset),
attribute.String("axiom.param.name", req.Name),
))
defer span.End()

var res VirtualFieldWithID
if err := s.client.Call(ctx, http.MethodPost, s.basePath, req, &res); err != nil {
return nil, spanError(span, err)
}

return &res, nil
}

// Update the virtual field identified by the given id with the given properties.
func (s *VirtualFieldsService) Update(ctx context.Context, id string, req VirtualField) (*VirtualFieldWithID, error) {
ctx, span := s.client.trace(ctx, "VirtualFields.Update", trace.WithAttributes(
attribute.String("axiom.virtual_field_id", id),
))
defer span.End()

path, err := url.JoinPath(s.basePath, id)
if err != nil {
return nil, spanError(span, err)
}

var res VirtualFieldWithID
if err := s.client.Call(ctx, http.MethodPut, path, req, &res); err != nil {
return nil, spanError(span, err)
}

return &res, nil
}

// Delete the virtual field identified by the given id.
func (s *VirtualFieldsService) Delete(ctx context.Context, id string) error {
ctx, span := s.client.trace(ctx, "VirtualFields.Delete", trace.WithAttributes(
attribute.String("axiom.virtual_field_id", id),
))
defer span.End()

path, err := url.JoinPath(s.basePath, id)
if err != nil {
return spanError(span, err)
}

if err := s.client.Call(ctx, http.MethodDelete, path, nil, nil); err != nil {
return spanError(span, err)
}

return nil
}
121 changes: 121 additions & 0 deletions axiom/vfields_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package axiom_test

import (
"context"
"testing"
"time"

"github.com/stretchr/testify/suite"

"github.com/axiomhq/axiom-go/axiom"
)

// VirtualFieldsTestSuite tests all methods of the Axiom Virtual Fields API
// against a live deployment.
type VirtualFieldsTestSuite struct {
IntegrationTestSuite

// Setup once per test.
vfield *axiom.VirtualFieldWithID
}

func TestVirtualFieldsTestSuite(t *testing.T) {
suite.Run(t, new(VirtualFieldsTestSuite))
}

func (s *VirtualFieldsTestSuite) SetupSuite() {
s.IntegrationTestSuite.SetupSuite()
}

func (s *VirtualFieldsTestSuite) TearDownSuite() {
s.IntegrationTestSuite.TearDownSuite()
}

func (s *VirtualFieldsTestSuite) SetupTest() {
s.IntegrationTestSuite.SetupTest()

var err error
s.vfield, err = s.client.VirtualFields.Create(s.ctx, axiom.VirtualField{
Dataset: "test-dataset",
Name: "TestField",
Expression: "a + b",
Type: "number",
})
s.Require().NoError(err)
s.Require().NotNil(s.vfield)
}

func (s *VirtualFieldsTestSuite) TearDownTest() {
// Teardown routines use their own context to avoid not being run at all
// when the suite gets cancelled or times out.
ctx, cancel := context.WithTimeout(context.WithoutCancel(s.ctx), time.Second*15)
defer cancel()

err := s.client.VirtualFields.Delete(ctx, s.vfield.ID)
s.NoError(err)

s.IntegrationTestSuite.TearDownTest()
}

func (s *VirtualFieldsTestSuite) Test() {
// Update the virtual field.
vfield, err := s.client.VirtualFields.Update(s.ctx, s.vfield.ID, axiom.VirtualField{
Dataset: "test-dataset",
Name: "UpdatedTestField",
Expression: "a - b",
Type: "number",
})
s.Require().NoError(err)
s.Require().NotNil(vfield)

s.vfield = vfield

// Get the virtual field and make sure it matches the updated values.
vfield, err = s.client.VirtualFields.Get(s.ctx, s.vfield.ID)
s.Require().NoError(err)
s.Require().NotNil(vfield)

s.Equal(s.vfield, vfield)

// List all virtual fields for the dataset and ensure the created field is part of the list.
vfields, err := s.client.VirtualFields.List(s.ctx, "test-dataset")
s.Require().NoError(err)
s.Require().NotEmpty(vfields)

s.Contains(vfields, s.vfield)
}

func (s *VirtualFieldsTestSuite) TestCreateAndDeleteVirtualField() {
// Create a new virtual field.
vfield, err := s.client.VirtualFields.Create(s.ctx, axiom.VirtualField{
Dataset: "test-dataset",
Name: "NewTestField",
Expression: "x * y",
Type: "number",
})
s.Require().NoError(err)
s.Require().NotNil(vfield)

// Get the virtual field and ensure it matches what was created.
fetchedField, err := s.client.VirtualFields.Get(s.ctx, vfield.ID)
s.Require().NoError(err)
s.Require().NotNil(fetchedField)
s.Equal(vfield, fetchedField)

// Delete the virtual field.
err = s.client.VirtualFields.Delete(s.ctx, vfield.ID)
s.Require().NoError(err)

// Ensure the virtual field no longer exists.
_, err = s.client.VirtualFields.Get(s.ctx, vfield.ID)
s.Error(err)
}

func (s *VirtualFieldsTestSuite) TestListVirtualFields() {
// List all virtual fields for the dataset and ensure the created field is part of the list.
vfields, err := s.client.VirtualFields.List(s.ctx, "test-dataset")
s.Require().NoError(err)
s.Require().NotEmpty(vfields)

s.Contains(vfields, s.vfield)
}
Loading