Skip to content

Commit

Permalink
feat: virtual fields to client
Browse files Browse the repository at this point in the history
  • Loading branch information
Rambatino committed Jan 10, 2025
1 parent 0720dba commit be05342
Show file tree
Hide file tree
Showing 4 changed files with 415 additions and 0 deletions.
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
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 {

Check failure on line 27 in axiom/vfields.go

View workflow job for this annotation

GitHub Actions / Lint (1.23)

var-naming: type VirtualFieldWithId should be VirtualFieldWithID (revive)
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: "Test Field",
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: "Updated Test Field",
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: "New Test Field",
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

0 comments on commit be05342

Please sign in to comment.