Skip to content

Commit 95db21b

Browse files
STAC-22985: List mappings and show their status
1 parent c59cfa9 commit 95db21b

34 files changed

+3417
-2
lines changed

cmd/otelcomponentmapping.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/stackvista/stackstate-cli/cmd/otelcomponentmapping"
6+
"github.com/stackvista/stackstate-cli/internal/di"
7+
)
8+
9+
func OtelComponentMappingCommand(deps *di.Deps) *cobra.Command {
10+
cmd := &cobra.Command{
11+
Use: "otel-component-mapping",
12+
Short: "Manage the Otel Component Mapping",
13+
Long: "Manage the Otel Component Mapping.",
14+
}
15+
16+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingListCommand(deps))
17+
cmd.AddCommand(otelcomponentmapping.OtelComponentMappingStatusCommand(deps))
18+
19+
return cmd
20+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"sort"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/stackvista/stackstate-cli/cmd/otelmapping"
8+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
9+
"github.com/stackvista/stackstate-cli/internal/common"
10+
"github.com/stackvista/stackstate-cli/internal/di"
11+
)
12+
13+
func OtelComponentMappingListCommand(deps *di.Deps) *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "list",
16+
Short: "Lists active Otel Component Mappings",
17+
Long: "Lists active Otel Component Mappings.",
18+
RunE: deps.CmdRunEWithApi(RunListComponentCommand),
19+
}
20+
21+
return cmd
22+
}
23+
24+
func RunListComponentCommand(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
25+
mappingsList, resp, err := api.OtelMappingApi.GetOtelComponentMappings(cli.Context).Execute()
26+
if err != nil {
27+
return common.NewResponseError(err, resp)
28+
}
29+
30+
sort.SliceStable(mappingsList, func(i, j int) bool {
31+
return mappingsList[i].Name < mappingsList[j].Name
32+
})
33+
34+
if cli.IsJson() {
35+
cli.Printer.PrintJson(map[string]interface{}{
36+
"otel component mappings": mappingsList,
37+
})
38+
} else {
39+
cli.Printer.Table(otelmapping.FormatOtelMappingTable(mappingsList))
40+
}
41+
42+
return nil
43+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/cmd/otelmapping"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
"github.com/stackvista/stackstate-cli/internal/printer"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestListOtelComponentMappingsJson(t *testing.T) {
13+
cli := di.NewMockDeps(t)
14+
cmd := OtelComponentMappingListCommand(&cli.Deps)
15+
cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingsResponse.Result = otelmapping.TestAllMappingItems
16+
17+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "-o", "json")
18+
19+
calls := *cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingsCalls
20+
assert.Len(t, calls, 1)
21+
22+
expected := []map[string]interface{}{
23+
{
24+
"otel component mappings": otelmapping.TestAllMappingItems,
25+
},
26+
}
27+
28+
assert.Equal(t, expected, *cli.MockPrinter.PrintJsonCalls)
29+
}
30+
31+
func TestOtelComponentMappingListTable(t *testing.T) {
32+
cli := di.NewMockDeps(t)
33+
cmd := OtelComponentMappingListCommand(&cli.Deps)
34+
cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingsResponse.Result = otelmapping.TestAllMappingItems
35+
36+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd)
37+
38+
calls := *cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingsCalls
39+
assert.Len(t, calls, 1)
40+
41+
expectedTableCall := []printer.TableData{
42+
{
43+
Header: []string{"Name", "Identifier"},
44+
Data: [][]interface{}{
45+
{otelmapping.TestSomeOtelMappingItem.Name, "identifier"},
46+
{otelmapping.TestSomeOtelMappingItem2.Name, "identifier2"},
47+
},
48+
MissingTableDataMsg: printer.NotFoundMsg{Types: "otel mappings"},
49+
},
50+
}
51+
52+
assert.Equal(t, expectedTableCall, *cli.MockPrinter.TableCalls)
53+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/stackvista/stackstate-cli/cmd/otelmapping"
6+
"github.com/stackvista/stackstate-cli/internal/common"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
)
9+
10+
// if cli.IsJson() {
11+
// cli.Printer.PrintJson(
12+
13+
func OtelComponentMappingStatusCommand(deps *di.Deps) *cobra.Command {
14+
args := &otelmapping.StatusArgs{}
15+
cmd := &cobra.Command{
16+
Use: "status",
17+
Short: "Get the status of an Otel Component Mappings",
18+
Long: "Get the status of an Otel Component Mappings.",
19+
RunE: deps.CmdRunEWithApi(otelmapping.RunStatus(args, "component", otelmapping.FetchComponentStatus)),
20+
}
21+
common.AddRequiredIdentifierFlagVar(cmd, &args.Identifier, "Identifier of the Otel Component Mapping")
22+
return cmd
23+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package otelcomponentmapping
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackvista/stackstate-cli/cmd/otelmapping"
7+
"github.com/stackvista/stackstate-cli/internal/di"
8+
"github.com/stackvista/stackstate-cli/internal/printer"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestOtelComponentMappingStatusJson(t *testing.T) {
13+
cli := di.NewMockDeps(t)
14+
cmd := OtelComponentMappingStatusCommand(&cli.Deps)
15+
cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingStatusResponse.Result = *otelmapping.TestSomeOtelMappingStatus
16+
17+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--identifier", "identifier", "-o", "json")
18+
19+
calls := *cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingStatusCalls
20+
assert.Len(t, calls, 1)
21+
22+
expected := []map[string]interface{}{
23+
{
24+
"otel-component-mapping": otelmapping.TestSomeOtelMappingStatus,
25+
},
26+
}
27+
28+
assert.Equal(t, expected, *cli.MockPrinter.PrintJsonCalls)
29+
}
30+
31+
func TestOtelComponentMappingStatusTable(t *testing.T) {
32+
cli := di.NewMockDeps(t)
33+
cmd := OtelComponentMappingStatusCommand(&cli.Deps)
34+
cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingStatusResponse.Result = *otelmapping.TestSomeOtelMappingStatus
35+
36+
di.ExecuteCommandWithContextUnsafe(&cli.Deps, cmd, "--identifier", "identifier")
37+
38+
calls := *cli.MockClient.ApiMocks.OtelMappingApi.GetOtelComponentMappingStatusCalls
39+
assert.Len(t, calls, 1)
40+
41+
expectedTableCall := []printer.TableData{
42+
{
43+
Header: []string{"Name", "Identifier", "Components", "Relations"},
44+
Data: [][]interface{}{
45+
{otelmapping.TestSomeOtelMappingStatusItem.Name, "identifier", otelmapping.TestComponentCount, otelmapping.TestRelationCount},
46+
},
47+
MissingTableDataMsg: printer.NotFoundMsg{Types: "otel mappings"},
48+
},
49+
{
50+
Header: []string{"Metric", "10s ago", "10-20s ago", "20-30s ago"},
51+
Data: [][]interface{}{
52+
{"latency seconds", otelmapping.TestMetricValue, otelmapping.TestMetricValue, otelmapping.TestMetricValue},
53+
},
54+
MissingTableDataMsg: printer.NotFoundMsg{Types: "metrics"},
55+
},
56+
{
57+
Header: []string{"Issue Id", "Level", "Message"},
58+
Data: [][]interface{}{
59+
{"-", otelmapping.TestError1.Level, otelmapping.TestError1.Message},
60+
{"-", otelmapping.TestError2.Level, otelmapping.TestError2.Message},
61+
},
62+
MissingTableDataMsg: printer.NotFoundMsg{Types: "otel component mapping errors"},
63+
},
64+
}
65+
66+
assert.Equal(t, expectedTableCall, *cli.MockPrinter.TableCalls)
67+
}

cmd/otelmapping/otelmapping.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package otelmapping
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
9+
"github.com/stackvista/stackstate-cli/internal/common"
10+
"github.com/stackvista/stackstate-cli/internal/di"
11+
"github.com/stackvista/stackstate-cli/internal/printer"
12+
"golang.org/x/text/cases"
13+
"golang.org/x/text/language"
14+
)
15+
16+
type StatusArgs struct {
17+
Identifier string
18+
}
19+
20+
func FormatOtelMappingStatusTable(otelmappings []stackstate_api.OtelMappingStatusItem) printer.TableData {
21+
data := make([][]interface{}, len(otelmappings))
22+
23+
for i, otelmapping := range otelmappings {
24+
identifier := "-"
25+
if otelmapping.HasIdentifier() {
26+
identifier = otelmapping.GetIdentifier()
27+
}
28+
data[i] = []interface{}{
29+
otelmapping.Name,
30+
identifier,
31+
otelmapping.ComponentCount,
32+
otelmapping.RelationCount,
33+
}
34+
}
35+
return printer.TableData{
36+
Header: []string{"Name", "Identifier", "Components", "Relations"},
37+
Data: data,
38+
MissingTableDataMsg: printer.NotFoundMsg{Types: "otel mappings"},
39+
}
40+
}
41+
42+
func FormatOtelMappingTable(otelmappings []stackstate_api.OtelMappingItem) printer.TableData {
43+
data := make([][]interface{}, len(otelmappings))
44+
45+
for i, otelmapping := range otelmappings {
46+
identifier := "-"
47+
if otelmapping.HasIdentifier() {
48+
identifier = otelmapping.GetIdentifier()
49+
}
50+
data[i] = []interface{}{
51+
otelmapping.Name,
52+
identifier,
53+
}
54+
}
55+
return printer.TableData{
56+
Header: []string{"Name", "Identifier"},
57+
Data: data,
58+
MissingTableDataMsg: printer.NotFoundMsg{Types: "otel mappings"},
59+
}
60+
}
61+
62+
type StatusFetcher func(cli *di.Deps, api *stackstate_api.APIClient, identifier string) (*stackstate_api.OtelMappingStatus, *http.Response, error)
63+
64+
func FetchComponentStatus(cli *di.Deps, api *stackstate_api.APIClient, identifier string) (*stackstate_api.OtelMappingStatus, *http.Response, error) {
65+
return api.OtelMappingApi.GetOtelComponentMappingStatus(cli.Context, identifier).Execute()
66+
}
67+
68+
func FetchRelationStatus(cli *di.Deps, api *stackstate_api.APIClient, identifier string) (*stackstate_api.OtelMappingStatus, *http.Response, error) {
69+
return api.OtelMappingApi.GetOtelRelationMappingStatus(cli.Context, identifier).Execute()
70+
}
71+
72+
func RunStatus(args *StatusArgs, mappingType string, fetch StatusFetcher) di.CmdWithApiFn {
73+
return func(cmd *cobra.Command, cli *di.Deps, api *stackstate_api.APIClient, serverInfo *stackstate_api.ServerInfo) common.CLIError {
74+
mappingStatus, resp, err := fetch(cli, api, args.Identifier)
75+
if err != nil {
76+
return common.NewResponseError(err, resp)
77+
}
78+
79+
jsonKey := fmt.Sprintf("otel-%s-mapping", mappingType)
80+
title := cases.Title(language.English).String(mappingType)
81+
mappingTitle := fmt.Sprintf("Otel %s Mapping:", title)
82+
metricsTitle := fmt.Sprintf("Otel %s Mapping Metrics:", title)
83+
errorsTitle := fmt.Sprintf("Otel %s Mapping Errors:", title)
84+
errorsNotFoundMsg := fmt.Sprintf("otel %s mapping errors", mappingType)
85+
86+
if cli.IsJson() {
87+
cli.Printer.PrintJson(map[string]interface{}{
88+
jsonKey: mappingStatus,
89+
})
90+
} else {
91+
cli.Printer.PrintLn("\n")
92+
cli.Printer.PrintLn(mappingTitle)
93+
cli.Printer.Table(FormatOtelMappingStatusTable([]stackstate_api.OtelMappingStatusItem{
94+
mappingStatus.Item,
95+
}))
96+
97+
if mappingStatus.HasMetrics() {
98+
cli.Printer.PrintLn("\n")
99+
cli.Printer.PrintLn(metricsTitle)
100+
size := mappingStatus.Metrics.BucketSizeSeconds
101+
cli.Printer.Table(printer.TableData{
102+
Header: []string{"Metric", fmt.Sprintf("%ds ago", size), fmt.Sprintf("%d-%ds ago", size, 2*size), fmt.Sprintf("%d-%ds ago", 2*size, 3*size)}, //nolint:mnd
103+
Data: [][]interface{}{
104+
printer.MetricBucketToRow("latency seconds", mappingStatus.Metrics.LatencySeconds),
105+
},
106+
MissingTableDataMsg: printer.NotFoundMsg{Types: "metrics"},
107+
})
108+
}
109+
110+
data := make([][]interface{}, len(mappingStatus.ErrorDetails))
111+
for i, error := range mappingStatus.ErrorDetails {
112+
id := "-"
113+
if error.HasIssueId() {
114+
id = *error.IssueId
115+
}
116+
data[i] = []interface{}{id, error.Level, error.Message}
117+
}
118+
119+
cli.Printer.PrintLn("\n")
120+
cli.Printer.PrintLn(errorsTitle)
121+
cli.Printer.Table(printer.TableData{
122+
Header: []string{"Issue Id", "Level", "Message"},
123+
Data: data,
124+
MissingTableDataMsg: printer.NotFoundMsg{Types: errorsNotFoundMsg},
125+
})
126+
}
127+
128+
return nil
129+
}
130+
}

cmd/otelmapping/test_data.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package otelmapping
2+
3+
import (
4+
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
5+
)
6+
7+
const (
8+
TestMetricValue = float64(23.5)
9+
TestComponentCount = int64(200)
10+
TestRelationCount = int64(100)
11+
)
12+
13+
var (
14+
TestError1 = stackstate_api.NewOtelMappingError(stackstate_api.MESSAGELEVEL_WARN, "Oops")
15+
TestError2 = stackstate_api.NewOtelMappingError(stackstate_api.MESSAGELEVEL_ERROR, "Oops2")
16+
TestSomeErrors = []stackstate_api.OtelMappingError{
17+
*TestError1,
18+
*TestError2,
19+
}
20+
TestMetricBucketValue = (func() *stackstate_api.MetricBucketValue {
21+
v := stackstate_api.NewMetricBucketValue()
22+
v.SetValue(float64(TestMetricValue))
23+
return v
24+
})()
25+
TestSomeMetrics = stackstate_api.OtelMappingMetrics{
26+
BucketSizeSeconds: 10,
27+
LatencySeconds: []stackstate_api.MetricBucketValue{*TestMetricBucketValue, *TestMetricBucketValue, *TestMetricBucketValue},
28+
}
29+
TestSomeOtelMappingStatusItem = (func() *stackstate_api.OtelMappingStatusItem {
30+
v := stackstate_api.NewOtelMappingStatusItem("name", TestRelationCount, TestComponentCount)
31+
v.SetIdentifier("identifier")
32+
return v
33+
})()
34+
TestSomeOtelMappingStatus = (func() *stackstate_api.OtelMappingStatus {
35+
v := stackstate_api.NewOtelMappingStatus(*TestSomeOtelMappingStatusItem, TestSomeErrors)
36+
v.SetMetrics(TestSomeMetrics)
37+
return v
38+
})()
39+
)
40+
41+
var (
42+
TestSomeOtelMappingItem = (func() *stackstate_api.OtelMappingItem {
43+
v := stackstate_api.NewOtelMappingItem("name")
44+
v.SetIdentifier("identifier")
45+
return v
46+
})()
47+
TestSomeOtelMappingItem2 = (func() *stackstate_api.OtelMappingItem {
48+
v := stackstate_api.NewOtelMappingItem("name2")
49+
v.SetIdentifier("identifier2")
50+
return v
51+
})()
52+
TestAllMappingItems = []stackstate_api.OtelMappingItem{*TestSomeOtelMappingItem, *TestSomeOtelMappingItem2}
53+
)

0 commit comments

Comments
 (0)