mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 07:38:58 +08:00
feat: QS package for integrations (#4578)
* chore: bring in latest state of QS api work for integrations * chore: integrations v0 qs API: refactor installed integration struct * chore: finish up with integration lifecycle tests * chore: some cleanup * chore: some more cleanup * chore: some more cleanup * chore: some more cleanup * chore: some more cleanup --------- Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
This commit is contained in:
parent
8f9d643923
commit
ddaa464d97
1
pkg/query-service/app/integrations/Readme.md
Normal file
1
pkg/query-service/app/integrations/Readme.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# SigNoz integrations
|
208
pkg/query-service/app/integrations/manager.go
Normal file
208
pkg/query-service/app/integrations/manager.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntegrationAuthor struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
HomePage string
|
||||||
|
}
|
||||||
|
type IntegrationSummary struct {
|
||||||
|
Id string
|
||||||
|
Title string
|
||||||
|
Description string // A short description
|
||||||
|
|
||||||
|
Author IntegrationAuthor
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationAssets struct {
|
||||||
|
// Each integration is expected to specify all log transformations
|
||||||
|
// in a single pipeline with a source based filter
|
||||||
|
LogPipeline *logparsingpipeline.PostablePipeline
|
||||||
|
|
||||||
|
// TBD: Dashboards, alerts, saved views, facets (indexed attribs)...
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationDetails struct {
|
||||||
|
IntegrationSummary
|
||||||
|
IntegrationAssets
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationsListItem struct {
|
||||||
|
IntegrationSummary
|
||||||
|
IsInstalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledIntegration struct {
|
||||||
|
IntegrationId string `db:"integration_id"`
|
||||||
|
Config InstalledIntegrationConfig `db:"config_json"`
|
||||||
|
InstalledAt time.Time `db:"installed_at"`
|
||||||
|
}
|
||||||
|
type InstalledIntegrationConfig map[string]interface{}
|
||||||
|
|
||||||
|
type Integration struct {
|
||||||
|
IntegrationDetails
|
||||||
|
Installation *InstalledIntegration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Manager struct {
|
||||||
|
availableIntegrationsRepo AvailableIntegrationsRepo
|
||||||
|
installedIntegrationsRepo InstalledIntegrationsRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntegrationsFilter struct {
|
||||||
|
IsInstalled *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ListIntegrations(
|
||||||
|
ctx context.Context,
|
||||||
|
filter *IntegrationsFilter,
|
||||||
|
// Expected to have pagination over time.
|
||||||
|
) ([]IntegrationsListItem, *model.ApiError) {
|
||||||
|
available, apiErr := m.availableIntegrationsRepo.list(ctx)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(
|
||||||
|
apiErr, "could not fetch available integrations",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
installed, apiErr := m.installedIntegrationsRepo.list(ctx)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(
|
||||||
|
apiErr, "could not fetch installed integrations",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
installedIds := []string{}
|
||||||
|
for _, ii := range installed {
|
||||||
|
installedIds = append(installedIds, ii.IntegrationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := []IntegrationsListItem{}
|
||||||
|
for _, ai := range available {
|
||||||
|
result = append(result, IntegrationsListItem{
|
||||||
|
IntegrationSummary: ai.IntegrationSummary,
|
||||||
|
IsInstalled: slices.Contains(installedIds, ai.Id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
if filter.IsInstalled != nil {
|
||||||
|
filteredResult := []IntegrationsListItem{}
|
||||||
|
for _, r := range result {
|
||||||
|
if r.IsInstalled == *filter.IsInstalled {
|
||||||
|
filteredResult = append(filteredResult, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = filteredResult
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetIntegration(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
) (*Integration, *model.ApiError) {
|
||||||
|
integrationDetails, apiErr := m.getIntegrationDetails(
|
||||||
|
ctx, integrationId,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
installation, apiErr := m.getInstalledIntegration(
|
||||||
|
ctx, integrationId,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Integration{
|
||||||
|
IntegrationDetails: *integrationDetails,
|
||||||
|
Installation: installation,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) InstallIntegration(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
config InstalledIntegrationConfig,
|
||||||
|
) (*IntegrationsListItem, *model.ApiError) {
|
||||||
|
integrationDetails, apiErr := m.getIntegrationDetails(ctx, integrationId)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
_, apiErr = m.installedIntegrationsRepo.upsert(
|
||||||
|
ctx, integrationId, config,
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(
|
||||||
|
apiErr, "could not insert installed integration",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IntegrationsListItem{
|
||||||
|
IntegrationSummary: integrationDetails.IntegrationSummary,
|
||||||
|
IsInstalled: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) UninstallIntegration(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
) *model.ApiError {
|
||||||
|
return m.installedIntegrationsRepo.delete(ctx, integrationId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers.
|
||||||
|
func (m *Manager) getIntegrationDetails(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
) (*IntegrationDetails, *model.ApiError) {
|
||||||
|
ais, apiErr := m.availableIntegrationsRepo.get(
|
||||||
|
ctx, []string{integrationId},
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, fmt.Sprintf(
|
||||||
|
"could not fetch integration: %s", integrationId,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
integrationDetails, wasFound := ais[integrationId]
|
||||||
|
if !wasFound {
|
||||||
|
return nil, model.NotFoundError(fmt.Errorf(
|
||||||
|
"could not find integration: %s", integrationId,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return &integrationDetails, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) getInstalledIntegration(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
) (*InstalledIntegration, *model.ApiError) {
|
||||||
|
iis, apiErr := m.installedIntegrationsRepo.get(
|
||||||
|
ctx, []string{integrationId},
|
||||||
|
)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, model.WrapApiError(apiErr, fmt.Sprintf(
|
||||||
|
"could not fetch installed integration: %s", integrationId,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
installation, wasFound := iis[integrationId]
|
||||||
|
if !wasFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &installation, nil
|
||||||
|
}
|
78
pkg/query-service/app/integrations/manager_test.go
Normal file
78
pkg/query-service/app/integrations/manager_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntegrationLifecycle(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
mgr := NewTestIntegrationsManager(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ii := true
|
||||||
|
installedIntegrationsFilter := &IntegrationsFilter{
|
||||||
|
IsInstalled: &ii,
|
||||||
|
}
|
||||||
|
|
||||||
|
installedIntegrations, apiErr := mgr.ListIntegrations(
|
||||||
|
ctx, installedIntegrationsFilter,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal([]IntegrationsListItem{}, installedIntegrations)
|
||||||
|
|
||||||
|
availableIntegrations, apiErr := mgr.ListIntegrations(ctx, nil)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(2, len(availableIntegrations))
|
||||||
|
require.False(availableIntegrations[0].IsInstalled)
|
||||||
|
require.False(availableIntegrations[1].IsInstalled)
|
||||||
|
|
||||||
|
testIntegrationConfig := map[string]interface{}{}
|
||||||
|
installed, apiErr := mgr.InstallIntegration(
|
||||||
|
ctx, availableIntegrations[1].Id, testIntegrationConfig,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(installed.Id, availableIntegrations[1].Id)
|
||||||
|
|
||||||
|
integration, apiErr := mgr.GetIntegration(ctx, availableIntegrations[1].Id)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(integration.Id, availableIntegrations[1].Id)
|
||||||
|
require.NotNil(integration.Installation)
|
||||||
|
|
||||||
|
installedIntegrations, apiErr = mgr.ListIntegrations(
|
||||||
|
ctx, installedIntegrationsFilter,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(1, len(installedIntegrations))
|
||||||
|
require.Equal(availableIntegrations[1].Id, installedIntegrations[0].Id)
|
||||||
|
|
||||||
|
availableIntegrations, apiErr = mgr.ListIntegrations(ctx, nil)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(2, len(availableIntegrations))
|
||||||
|
require.False(availableIntegrations[0].IsInstalled)
|
||||||
|
require.True(availableIntegrations[1].IsInstalled)
|
||||||
|
|
||||||
|
apiErr = mgr.UninstallIntegration(ctx, installed.Id)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
|
||||||
|
integration, apiErr = mgr.GetIntegration(ctx, availableIntegrations[1].Id)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(integration.Id, availableIntegrations[1].Id)
|
||||||
|
require.Nil(integration.Installation)
|
||||||
|
|
||||||
|
installedIntegrations, apiErr = mgr.ListIntegrations(
|
||||||
|
ctx, installedIntegrationsFilter,
|
||||||
|
)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(0, len(installedIntegrations))
|
||||||
|
|
||||||
|
availableIntegrations, apiErr = mgr.ListIntegrations(ctx, nil)
|
||||||
|
require.Nil(apiErr)
|
||||||
|
require.Equal(2, len(availableIntegrations))
|
||||||
|
require.False(availableIntegrations[0].IsInstalled)
|
||||||
|
require.False(availableIntegrations[1].IsInstalled)
|
||||||
|
}
|
58
pkg/query-service/app/integrations/repo.go
Normal file
58
pkg/query-service/app/integrations/repo.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For serializing from db
|
||||||
|
func (c *InstalledIntegrationConfig) Scan(src interface{}) error {
|
||||||
|
if data, ok := src.([]byte); ok {
|
||||||
|
return json.Unmarshal(data, &c)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For serializing to db
|
||||||
|
func (c *InstalledIntegrationConfig) Value() (driver.Value, error) {
|
||||||
|
filterSetJson, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "could not serialize integration config to JSON")
|
||||||
|
}
|
||||||
|
return filterSetJson, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledIntegrationsRepo interface {
|
||||||
|
list(context.Context) ([]InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
|
get(
|
||||||
|
ctx context.Context, integrationIds []string,
|
||||||
|
) (map[string]InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
|
upsert(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
config InstalledIntegrationConfig,
|
||||||
|
) (*InstalledIntegration, *model.ApiError)
|
||||||
|
|
||||||
|
delete(ctx context.Context, integrationId string) *model.ApiError
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvailableIntegrationsRepo interface {
|
||||||
|
list(context.Context) ([]IntegrationDetails, *model.ApiError)
|
||||||
|
|
||||||
|
get(
|
||||||
|
ctx context.Context, integrationIds []string,
|
||||||
|
) (map[string]IntegrationDetails, *model.ApiError)
|
||||||
|
|
||||||
|
// AvailableIntegrationsRepo implementations are expected to cache
|
||||||
|
// details of installed integrations for quick retrieval.
|
||||||
|
//
|
||||||
|
// For v0 only bundled integrations are available, later versions
|
||||||
|
// are expected to add methods in this interface for pinning installed
|
||||||
|
// integration details in local cache.
|
||||||
|
}
|
168
pkg/query-service/app/integrations/sqlite_repo.go
Normal file
168
pkg/query-service/app/integrations/sqlite_repo.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitSqliteDBIfNeeded(db *sqlx.DB) error {
|
||||||
|
if db == nil {
|
||||||
|
return fmt.Errorf("db is required.")
|
||||||
|
}
|
||||||
|
|
||||||
|
createTablesStatements := `
|
||||||
|
CREATE TABLE IF NOT EXISTS integrations_installed(
|
||||||
|
integration_id TEXT PRIMARY KEY,
|
||||||
|
config_json TEXT,
|
||||||
|
installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
`
|
||||||
|
_, err := db.Exec(createTablesStatements)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"could not ensure integrations schema in sqlite DB: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type InstalledIntegrationsSqliteRepo struct {
|
||||||
|
db *sqlx.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInstalledIntegrationsSqliteRepo(db *sqlx.DB) (
|
||||||
|
*InstalledIntegrationsSqliteRepo, error,
|
||||||
|
) {
|
||||||
|
err := InitSqliteDBIfNeeded(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"couldn't ensure sqlite schema for installed integrations: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &InstalledIntegrationsSqliteRepo{
|
||||||
|
db: db,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InstalledIntegrationsSqliteRepo) list(
|
||||||
|
ctx context.Context,
|
||||||
|
) ([]InstalledIntegration, *model.ApiError) {
|
||||||
|
integrations := []InstalledIntegration{}
|
||||||
|
|
||||||
|
err := r.db.SelectContext(
|
||||||
|
ctx, &integrations, `
|
||||||
|
select
|
||||||
|
integration_id,
|
||||||
|
config_json,
|
||||||
|
installed_at
|
||||||
|
from integrations_installed
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf(
|
||||||
|
"could not query installed integrations: %w", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return integrations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InstalledIntegrationsSqliteRepo) get(
|
||||||
|
ctx context.Context, integrationIds []string,
|
||||||
|
) (map[string]InstalledIntegration, *model.ApiError) {
|
||||||
|
integrations := []InstalledIntegration{}
|
||||||
|
|
||||||
|
idPlaceholders := []string{}
|
||||||
|
idValues := []interface{}{}
|
||||||
|
for _, id := range integrationIds {
|
||||||
|
idPlaceholders = append(idPlaceholders, "?")
|
||||||
|
idValues = append(idValues, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.db.SelectContext(
|
||||||
|
ctx, &integrations, fmt.Sprintf(`
|
||||||
|
select
|
||||||
|
integration_id,
|
||||||
|
config_json,
|
||||||
|
installed_at
|
||||||
|
from integrations_installed
|
||||||
|
where integration_id in (%s)`,
|
||||||
|
strings.Join(idPlaceholders, ", "),
|
||||||
|
),
|
||||||
|
idValues...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf(
|
||||||
|
"could not query installed integrations: %w", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]InstalledIntegration{}
|
||||||
|
for _, ii := range integrations {
|
||||||
|
result[ii.IntegrationId] = ii
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InstalledIntegrationsSqliteRepo) upsert(
|
||||||
|
ctx context.Context,
|
||||||
|
integrationId string,
|
||||||
|
config InstalledIntegrationConfig,
|
||||||
|
) (*InstalledIntegration, *model.ApiError) {
|
||||||
|
serializedConfig, err := config.Value()
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(
|
||||||
|
"could not serialize integration config: %w", err,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, dbErr := r.db.ExecContext(
|
||||||
|
ctx, `
|
||||||
|
INSERT INTO integrations_installed (
|
||||||
|
integration_id,
|
||||||
|
config_json
|
||||||
|
) values ($1, $2)
|
||||||
|
on conflict(integration_id) do update
|
||||||
|
set config_json=excluded.config_json
|
||||||
|
`, integrationId, serializedConfig,
|
||||||
|
)
|
||||||
|
if dbErr != nil {
|
||||||
|
return nil, model.InternalError(fmt.Errorf(
|
||||||
|
"could not insert record for integration installation: %w", dbErr,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, apiErr := r.get(ctx, []string{integrationId})
|
||||||
|
if apiErr != nil || len(res) < 1 {
|
||||||
|
return nil, model.WrapApiError(
|
||||||
|
apiErr, "could not fetch installed integration",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
installed := res[integrationId]
|
||||||
|
|
||||||
|
return &installed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *InstalledIntegrationsSqliteRepo) delete(
|
||||||
|
ctx context.Context, integrationId string,
|
||||||
|
) *model.ApiError {
|
||||||
|
_, dbErr := r.db.ExecContext(ctx, `
|
||||||
|
DELETE FROM integrations_installed where integration_id = ?
|
||||||
|
`, integrationId)
|
||||||
|
|
||||||
|
if dbErr != nil {
|
||||||
|
return model.InternalError(fmt.Errorf(
|
||||||
|
"could not delete installed integration record for %s: %w",
|
||||||
|
integrationId, dbErr,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
161
pkg/query-service/app/integrations/test_utils.go
Normal file
161
pkg/query-service/app/integrations/test_utils.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
package integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/model"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTestSqliteDB(t *testing.T) (
|
||||||
|
db *sqlx.DB, dbFilePath string,
|
||||||
|
) {
|
||||||
|
testDBFile, err := os.CreateTemp("", "test-signoz-db-*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create temp file for test db: %v", err)
|
||||||
|
}
|
||||||
|
testDBFilePath := testDBFile.Name()
|
||||||
|
t.Cleanup(func() { os.Remove(testDBFilePath) })
|
||||||
|
testDBFile.Close()
|
||||||
|
|
||||||
|
testDB, err := sqlx.Open("sqlite3", testDBFilePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not open test db sqlite file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return testDB, testDBFilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestIntegrationsManager(t *testing.T) *Manager {
|
||||||
|
testDB, _ := NewTestSqliteDB(t)
|
||||||
|
|
||||||
|
installedIntegrationsRepo, err := NewInstalledIntegrationsSqliteRepo(testDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not init sqlite DB for installed integrations: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Manager{
|
||||||
|
availableIntegrationsRepo: &TestAvailableIntegrationsRepo{},
|
||||||
|
installedIntegrationsRepo: installedIntegrationsRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestAvailableIntegrationsRepo struct{}
|
||||||
|
|
||||||
|
func (t *TestAvailableIntegrationsRepo) list(
|
||||||
|
ctx context.Context,
|
||||||
|
) ([]IntegrationDetails, *model.ApiError) {
|
||||||
|
return []IntegrationDetails{
|
||||||
|
{
|
||||||
|
IntegrationSummary: IntegrationSummary{
|
||||||
|
Id: "test-integration-1",
|
||||||
|
Title: "Test Integration 1",
|
||||||
|
Description: "A test integration",
|
||||||
|
Author: IntegrationAuthor{
|
||||||
|
Name: "signoz",
|
||||||
|
Email: "integrations@signoz.io",
|
||||||
|
HomePage: "https://signoz.io",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IntegrationAssets: IntegrationAssets{
|
||||||
|
LogPipeline: &logparsingpipeline.PostablePipeline{
|
||||||
|
Name: "pipeline1",
|
||||||
|
Alias: "pipeline1",
|
||||||
|
Enabled: true,
|
||||||
|
Filter: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "method",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
Operator: "=",
|
||||||
|
Value: "GET",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Config: []logparsingpipeline.PipelineOperator{
|
||||||
|
{
|
||||||
|
OrderId: 1,
|
||||||
|
ID: "add",
|
||||||
|
Type: "add",
|
||||||
|
Field: "attributes.test",
|
||||||
|
Value: "val",
|
||||||
|
Enabled: true,
|
||||||
|
Name: "test add",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
IntegrationSummary: IntegrationSummary{
|
||||||
|
Id: "test-integration-2",
|
||||||
|
Title: "Test Integration 2",
|
||||||
|
Description: "Another test integration",
|
||||||
|
Author: IntegrationAuthor{
|
||||||
|
Name: "signoz",
|
||||||
|
Email: "integrations@signoz.io",
|
||||||
|
HomePage: "https://signoz.io",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
IntegrationAssets: IntegrationAssets{
|
||||||
|
LogPipeline: &logparsingpipeline.PostablePipeline{
|
||||||
|
Name: "pipeline2",
|
||||||
|
Alias: "pipeline2",
|
||||||
|
Enabled: true,
|
||||||
|
Filter: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "method",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
Operator: "=",
|
||||||
|
Value: "GET",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Config: []logparsingpipeline.PipelineOperator{
|
||||||
|
{
|
||||||
|
OrderId: 1,
|
||||||
|
ID: "add",
|
||||||
|
Type: "add",
|
||||||
|
Field: "attributes.test",
|
||||||
|
Value: "val",
|
||||||
|
Enabled: true,
|
||||||
|
Name: "test add",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestAvailableIntegrationsRepo) get(
|
||||||
|
ctx context.Context, ids []string,
|
||||||
|
) (map[string]IntegrationDetails, *model.ApiError) {
|
||||||
|
availableIntegrations, apiErr := t.list(ctx)
|
||||||
|
if apiErr != nil {
|
||||||
|
return nil, apiErr
|
||||||
|
}
|
||||||
|
|
||||||
|
result := map[string]IntegrationDetails{}
|
||||||
|
|
||||||
|
for _, ai := range availableIntegrations {
|
||||||
|
if slices.Contains(ids, ai.Id) {
|
||||||
|
result[ai.Id] = ai
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user