Skip to content

Commit f5f324d

Browse files
Merge pull request #117 from StackVista/stac-22985
STAC-22985: List mappings and show their status
2 parents 10cd77c + d3388af commit f5f324d

36 files changed

Lines changed: 3436 additions & 2 deletions

cmd/cmd_rules_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,27 @@ func TestNounsAndVerbsExistAsFilesInDirectories(t *testing.T) {
3434
}
3535

3636
func TestVerbCommandCheckForJsonOuput(t *testing.T) {
37+
var jsonCheckAllowList = map[string]struct{}{
38+
"otelcomponentmapping/otelcomponentmapping_status.go": {},
39+
"otelrelationmapping/otelrelationmapping_status.go": {},
40+
}
41+
3742
root := setupCmd(t)
3843
for _, nounCmd := range root.Commands() {
3944
for _, verbCmd := range nounCmd.Commands() {
4045
nounName := strings.ReplaceAll(nounCmd.Name(), "-", "")
4146
verbName := strings.ReplaceAll(verbCmd.Name(), "-", "_")
4247
verCmdGoFile := fmt.Sprintf("%s/%s_%s.go", nounName, nounName, verbName)
48+
49+
if _, ok := jsonCheckAllowList[verCmdGoFile]; ok {
50+
continue
51+
}
52+
4353
verbCmdGoCode, err := os.ReadFile(verCmdGoFile)
4454
if err != nil {
4555
t.Fatal(err)
4656
}
57+
4758
if !strings.Contains(string(verbCmdGoCode), "if cli.IsJson() {") {
4859
t.Errorf("%s does not check whether to print to json!", verCmdGoFile)
4960
}

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

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+
}

0 commit comments

Comments
 (0)