diff --git a/confluence/api_client_impl.go b/confluence/api_client_impl.go index d9a917b5..0254be83 100644 --- a/confluence/api_client_impl.go +++ b/confluence/api_client_impl.go @@ -55,19 +55,21 @@ func New(httpClient common.HttpClient, site string) (*Client, error) { client.Label = internal.NewLabelService(client) client.Search = internal.NewSearchService(client) client.LongTask = internal.NewTaskService(client) + client.Analytics = internal.NewAnalyticsService(client) return client, nil } type Client struct { - HTTP common.HttpClient - Site *url.URL - Auth common.Authentication - Content *internal.ContentService - Space *internal.SpaceService - Label *internal.LabelService - Search *internal.SearchService - LongTask *internal.TaskService + HTTP common.HttpClient + Site *url.URL + Auth common.Authentication + Content *internal.ContentService + Space *internal.SpaceService + Label *internal.LabelService + Search *internal.SearchService + LongTask *internal.TaskService + Analytics *internal.AnalyticsService } func (c *Client) NewFormRequest(ctx context.Context, method, apiEndpoint, contentType string, payload io.Reader) (*http.Request, error) { diff --git a/confluence/internal/analytics_impl.go b/confluence/internal/analytics_impl.go new file mode 100644 index 00000000..32b8fa66 --- /dev/null +++ b/confluence/internal/analytics_impl.go @@ -0,0 +1,105 @@ +package internal + +import ( + "context" + "fmt" + model "github.com/ctreminiom/go-atlassian/pkg/infra/models" + "github.com/ctreminiom/go-atlassian/service" + "github.com/ctreminiom/go-atlassian/service/confluence" + "net/http" + "net/url" + "strings" +) + +func NewAnalyticsService(client service.Client) *AnalyticsService { + + return &AnalyticsService{ + internalClient: &internalAnalyticsServiceImpl{c: client}, + } +} + +type AnalyticsService struct { + internalClient confluence.AnalyticsConnector +} + +// Get gets the total number of views a piece of content has. +// +// GET /wiki/rest/api/analytics/content/{contentId}/views +// +// https://docs.go-atlassian.io/confluence-cloud/analytics#get-views +func (a *AnalyticsService) Get(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) { + return a.internalClient.Get(ctx, contentId, fromDate) +} + +// Distinct get the total number of distinct viewers a piece of content has. +// +// GET /wiki/rest/api/analytics/content/{contentId}/viewers +// +// https://docs.go-atlassian.io/confluence-cloud/analytics#get-viewers +func (a *AnalyticsService) Distinct(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) { + return a.internalClient.Distinct(ctx, contentId, fromDate) +} + +type internalAnalyticsServiceImpl struct { + c service.Client +} + +func (i *internalAnalyticsServiceImpl) Get(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) { + + if contentId == "" { + return nil, nil, model.ErrNoContentIDError + } + + var endpoint strings.Builder + endpoint.WriteString(fmt.Sprintf("wiki/rest/api/analytics/content/%v/views", contentId)) + + if fromDate != "" { + query := url.Values{} + query.Add("fromDate", fromDate) + + endpoint.WriteString(fmt.Sprintf("?%v", query.Encode())) + } + + request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, nil, err + } + + views := new(model.ContentViewScheme) + response, err := i.c.Call(request, views) + if err != nil { + return nil, response, err + } + + return views, response, nil +} + +func (i *internalAnalyticsServiceImpl) Distinct(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) { + + if contentId == "" { + return nil, nil, model.ErrNoContentIDError + } + + var endpoint strings.Builder + endpoint.WriteString(fmt.Sprintf("wiki/rest/api/analytics/content/%v/viewers", contentId)) + + if fromDate != "" { + query := url.Values{} + query.Add("fromDate", fromDate) + + endpoint.WriteString(fmt.Sprintf("?%v", query.Encode())) + } + + request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return nil, nil, err + } + + views := new(model.ContentViewScheme) + response, err := i.c.Call(request, views) + if err != nil { + return nil, response, err + } + + return views, response, nil +} diff --git a/confluence/internal/analytics_impl_test.go b/confluence/internal/analytics_impl_test.go new file mode 100644 index 00000000..edc3499a --- /dev/null +++ b/confluence/internal/analytics_impl_test.go @@ -0,0 +1,232 @@ +package internal + +import ( + "context" + "errors" + model "github.com/ctreminiom/go-atlassian/pkg/infra/models" + "github.com/ctreminiom/go-atlassian/service" + "github.com/ctreminiom/go-atlassian/service/mocks" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func Test_internalAnalyticsServiceImpl_Get(t *testing.T) { + + type fields struct { + c service.Client + } + + type args struct { + ctx context.Context + contentId, fromDate string + } + + testCases := []struct { + name string + fields fields + args args + on func(*fields) + wantErr bool + Err error + }{ + { + name: "when the parameters are correct", + args: args{ + ctx: context.TODO(), + contentId: "2337372172371", + fromDate: "2023-10-03", + }, + on: func(fields *fields) { + + client := mocks.NewClient(t) + + client.On("NewRequest", + context.Background(), + http.MethodGet, + "wiki/rest/api/analytics/content/2337372172371/views?fromDate=2023-10-03", + nil). + Return(&http.Request{}, nil) + + client.On("Call", + &http.Request{}, + &model.ContentViewScheme{}). + Return(&model.ResponseScheme{}, nil) + + fields.c = client + }, + }, + + { + name: "when the http request cannot be created", + args: args{ + ctx: context.TODO(), + contentId: "2337372172371", + fromDate: "2023-10-03", + }, + on: func(fields *fields) { + + client := mocks.NewClient(t) + + client.On("NewRequest", + context.Background(), + http.MethodGet, + "wiki/rest/api/analytics/content/2337372172371/views?fromDate=2023-10-03", + nil). + Return(&http.Request{}, errors.New("error, unable to create the http request")) + + fields.c = client + + }, + wantErr: true, + Err: errors.New("error, unable to create the http request"), + }, + + { + name: "when the content id is not provided", + args: args{ + ctx: context.TODO(), + }, + wantErr: true, + Err: model.ErrNoContentIDError, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + + if testCase.on != nil { + testCase.on(&testCase.fields) + } + + newService := NewAnalyticsService(testCase.fields.c) + + gotResult, gotResponse, err := newService.Get(testCase.args.ctx, testCase.args.contentId, testCase.args.fromDate) + + if testCase.wantErr { + + if err != nil { + t.Logf("error returned: %v", err.Error()) + } + + assert.EqualError(t, err, testCase.Err.Error()) + } else { + + assert.NoError(t, err) + assert.NotEqual(t, gotResponse, nil) + assert.NotEqual(t, gotResult, nil) + } + + }) + } +} + +func Test_internalAnalyticsServiceImpl_Distinct(t *testing.T) { + + type fields struct { + c service.Client + } + + type args struct { + ctx context.Context + contentId, fromDate string + } + + testCases := []struct { + name string + fields fields + args args + on func(*fields) + wantErr bool + Err error + }{ + { + name: "when the parameters are correct", + args: args{ + ctx: context.TODO(), + contentId: "2337372172371", + fromDate: "2023-10-03", + }, + on: func(fields *fields) { + + client := mocks.NewClient(t) + + client.On("NewRequest", + context.Background(), + http.MethodGet, + "wiki/rest/api/analytics/content/2337372172371/viewers?fromDate=2023-10-03", + nil). + Return(&http.Request{}, nil) + + client.On("Call", + &http.Request{}, + &model.ContentViewScheme{}). + Return(&model.ResponseScheme{}, nil) + + fields.c = client + }, + }, + + { + name: "when the http request cannot be created", + args: args{ + ctx: context.TODO(), + contentId: "2337372172371", + fromDate: "2023-10-03", + }, + on: func(fields *fields) { + + client := mocks.NewClient(t) + + client.On("NewRequest", + context.Background(), + http.MethodGet, + "wiki/rest/api/analytics/content/2337372172371/viewers?fromDate=2023-10-03", + nil). + Return(&http.Request{}, errors.New("error, unable to create the http request")) + + fields.c = client + + }, + wantErr: true, + Err: errors.New("error, unable to create the http request"), + }, + + { + name: "when the content id is not provided", + args: args{ + ctx: context.TODO(), + }, + wantErr: true, + Err: model.ErrNoContentIDError, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + + if testCase.on != nil { + testCase.on(&testCase.fields) + } + + newService := NewAnalyticsService(testCase.fields.c) + + gotResult, gotResponse, err := newService.Distinct(testCase.args.ctx, testCase.args.contentId, testCase.args.fromDate) + + if testCase.wantErr { + + if err != nil { + t.Logf("error returned: %v", err.Error()) + } + + assert.EqualError(t, err, testCase.Err.Error()) + } else { + + assert.NoError(t, err) + assert.NotEqual(t, gotResponse, nil) + assert.NotEqual(t, gotResult, nil) + } + + }) + } +} diff --git a/pkg/infra/models/confluence_analytics.go b/pkg/infra/models/confluence_analytics.go new file mode 100644 index 00000000..ae0070f3 --- /dev/null +++ b/pkg/infra/models/confluence_analytics.go @@ -0,0 +1,6 @@ +package models + +type ContentViewScheme struct { + ID int `json:"id,omitempty"` + Count int `json:"count,omitempty"` +} diff --git a/service/confluence/analytics.go b/service/confluence/analytics.go new file mode 100644 index 00000000..0d99b01e --- /dev/null +++ b/service/confluence/analytics.go @@ -0,0 +1,23 @@ +package confluence + +import ( + "context" + model "github.com/ctreminiom/go-atlassian/pkg/infra/models" +) + +type AnalyticsConnector interface { + + // Get gets the total number of views a piece of content has. + // + // GET /wiki/rest/api/analytics/content/{contentId}/views + // + // https://docs.go-atlassian.io/confluence-cloud/analytics#get-views + Get(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) + + // Distinct get the total number of distinct viewers a piece of content has. + // + // GET /wiki/rest/api/analytics/content/{contentId}/viewers + // + // https://docs.go-atlassian.io/confluence-cloud/analytics#get-viewers + Distinct(ctx context.Context, contentId, fromDate string) (*model.ContentViewScheme, *model.ResponseScheme, error) +}