Skip to content

Commit 36c7d5b

Browse files
committed
fix: flag list display polish
Address flag list feedback (applied consistently to flag get and segment detail where the same fields appear): - feature type shown lower-case (standard/multivariate, not STANDARD) - the ENABLED true/false column is now STATE on/off - long values are truncated in the table (values can be huge); the detail views still show the full value - a null identity-override count (Edge/Dynamo projects) shows 0, not - beep boop
1 parent 67ec00b commit 36c7d5b

2 files changed

Lines changed: 92 additions & 12 deletions

File tree

internal/cmd/cmd_test.go

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,8 +1637,8 @@ func TestFlagsList(t *testing.T) {
16371637
t.Fatalf("flags list: %v\noutput: %s", err, out)
16381638
}
16391639
for _, want := range []string{
1640-
"NAME", "TYPE", "ENABLED", "VALUE", "LIFECYCLE",
1641-
"onboarding_banner", "STANDARD", "true", "live",
1640+
"NAME", "TYPE", "STATE", "VALUE", "LIFECYCLE",
1641+
"onboarding_banner", "standard", "on", "live",
16421642
"max_items", "25", "2 flags",
16431643
} {
16441644
if !strings.Contains(out, want) {
@@ -1732,6 +1732,34 @@ func TestFlagsList(t *testing.T) {
17321732
t.Errorf("err = %v, want a missing-environment error", err)
17331733
}
17341734
})
1735+
1736+
t.Run("off state and a truncated long value", func(t *testing.T) {
1737+
// Given a disabled flag with a very long value
1738+
isolateStorage(t)
1739+
f := newFakeInstance(t)
1740+
long := strings.Repeat("x", 200)
1741+
f.features["101"] = []map[string]any{{
1742+
"id": 1, "name": "blob", "type": "MULTIVARIATE",
1743+
"environment_feature_state": map[string]any{"enabled": false, "feature_state_value": long},
1744+
}}
1745+
root := tempRepo(t)
1746+
writeConfig(t, root, `{"project": 101, "environment": "WqXhZk8sVY3dGgTqZ9pJmN", "apiUrl": "`+f.srv.URL+`"}`)
1747+
t.Setenv("FLAGSMITH_API_KEY", masterKey)
1748+
1749+
// When
1750+
out, err := run("", "flag", "list")
1751+
1752+
// Then — off, lower-case type, and no full 200-char value
1753+
if err != nil {
1754+
t.Fatalf("flags list: %v", err)
1755+
}
1756+
if !strings.Contains(out, "off") || !strings.Contains(out, "multivariate") || !strings.Contains(out, "…") {
1757+
t.Errorf("output = %q, want off/multivariate/truncation", out)
1758+
}
1759+
if strings.Contains(out, long) {
1760+
t.Errorf("output = %q, want the long value truncated", out)
1761+
}
1762+
})
17351763
}
17361764

17371765
func TestFlagGet(t *testing.T) {
@@ -1800,6 +1828,31 @@ func TestFlagGet(t *testing.T) {
18001828
t.Errorf("err = %v, want a not-found error naming the feature", err)
18011829
}
18021830
})
1831+
1832+
t.Run("a null identity-override count shows 0", func(t *testing.T) {
1833+
// Given num_identity_overrides is null (Edge/Dynamo projects)
1834+
isolateStorage(t)
1835+
f := newFakeInstance(t)
1836+
f.features["101"] = []map[string]any{{
1837+
"id": 1, "name": "edgeflag", "type": "STANDARD",
1838+
"num_segment_overrides": 0, "num_identity_overrides": nil,
1839+
"environment_feature_state": map[string]any{"enabled": true, "feature_state_value": "x"},
1840+
}}
1841+
root := tempRepo(t)
1842+
writeConfig(t, root, `{"project": 101, "environment": "WqXhZk8sVY3dGgTqZ9pJmN", "apiUrl": "`+f.srv.URL+`"}`)
1843+
t.Setenv("FLAGSMITH_API_KEY", masterKey)
1844+
1845+
// When
1846+
out, err := run("", "flag", "get", "edgeflag")
1847+
1848+
// Then — shown as 0, not "-"
1849+
if err != nil {
1850+
t.Fatalf("flag get: %v", err)
1851+
}
1852+
if !strings.Contains(out, "Identity overrides") || !strings.Contains(out, "0") {
1853+
t.Errorf("output = %q, want Identity overrides 0", out)
1854+
}
1855+
})
18031856
}
18041857

18051858
// flagUpdateEnv writes a config bound to project 101 / Development and returns
@@ -1959,7 +2012,7 @@ func TestFlagGetSegment(t *testing.T) {
19592012
if got := f.featuresSeg(); got != "12" {
19602013
t.Errorf("features segment = %q, want 12", got)
19612014
}
1962-
for _, want := range []string{"Segment", "12", "special", "true"} {
2015+
for _, want := range []string{"Segment", "12", "special", "on"} {
19632016
if !strings.Contains(out, want) {
19642017
t.Errorf("output = %q, want %q", out, want)
19652018
}

internal/cmd/flags.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ func flagEnabled(fs *api.FeatureState) bool {
2828
return fs != nil && fs.Enabled
2929
}
3030

31+
// stateLabel renders a flag's on/off state.
32+
func stateLabel(fs *api.FeatureState) string {
33+
if flagEnabled(fs) {
34+
return "on"
35+
}
36+
return "off"
37+
}
38+
39+
// featureTypeLabel lower-cases the feature type for display (the API returns
40+
// e.g. STANDARD / MULTIVARIATE).
41+
func featureTypeLabel(t string) string {
42+
return strings.ToLower(t)
43+
}
44+
45+
// valueDisplayMax bounds how wide a flag value is shown in the list table;
46+
// values can be very large (JSON blobs, long strings).
47+
const valueDisplayMax = 40
48+
49+
// truncateValue shortens an over-long value for table display, marking the cut.
50+
func truncateValue(s string) string {
51+
r := []rune(s)
52+
if len(r) <= valueDisplayMax {
53+
return s
54+
}
55+
return string(r[:valueDisplayMax-1]) + "…"
56+
}
57+
3158
// flagContext resolves the credential, project, and environment every flag
3259
// command needs.
3360
func flagContext(cmd *cobra.Command) (*projectContext, *activeCredential, int, api.Environment, error) {
@@ -71,13 +98,13 @@ var flagListCmd = &cobra.Command{
7198
for i, f := range features {
7299
rows[i] = []string{
73100
f.Name,
74-
f.Type,
75-
strconv.FormatBool(flagEnabled(f.EnvironmentState)),
76-
flagValue(f.EnvironmentState),
101+
featureTypeLabel(f.Type),
102+
stateLabel(f.EnvironmentState),
103+
truncateValue(flagValue(f.EnvironmentState)),
77104
lifecycleOrDash(f.LifecycleStage),
78105
}
79106
}
80-
if err := output.Table(w, []string{"NAME", "TYPE", "ENABLED", "VALUE", "LIFECYCLE"}, rows); err != nil {
107+
if err := output.Table(w, []string{"NAME", "TYPE", "STATE", "VALUE", "LIFECYCLE"}, rows); err != nil {
81108
return err
82109
}
83110
fmt.Fprintf(w, "\n%d %s\n", len(features), plural(len(features), "flag", "flags"))
@@ -134,9 +161,9 @@ func renderSegmentDetail(cmd *cobra.Command, feature *api.Feature, segmentID int
134161
return output.Render(cmd.OutOrStdout(), feature, outputOpts(), func(w io.Writer) error {
135162
return output.Detail(w, []output.Field{
136163
{Label: "Feature", Value: feature.Name},
137-
{Label: "Type", Value: feature.Type},
164+
{Label: "Type", Value: featureTypeLabel(feature.Type)},
138165
{Label: "Segment", Value: strconv.Itoa(segmentID)},
139-
{Label: "Enabled", Value: strconv.FormatBool(flagEnabled(feature.SegmentState))},
166+
{Label: "State", Value: stateLabel(feature.SegmentState)},
140167
{Label: "Value", Value: flagValue(feature.SegmentState)},
141168
})
142169
})
@@ -148,8 +175,8 @@ func renderFlagDetail(cmd *cobra.Command, feature *api.Feature) error {
148175
return output.Detail(w, []output.Field{
149176
{Label: "Feature", Value: feature.Name},
150177
{Label: "Description", Value: feature.Description},
151-
{Label: "Type", Value: feature.Type},
152-
{Label: "Enabled", Value: strconv.FormatBool(flagEnabled(feature.EnvironmentState))},
178+
{Label: "Type", Value: featureTypeLabel(feature.Type)},
179+
{Label: "State", Value: stateLabel(feature.EnvironmentState)},
153180
{Label: "Value", Value: flagValue(feature.EnvironmentState)},
154181
{Label: "Segment overrides", Value: strconv.Itoa(feature.NumSegmentOverrides)},
155182
{Label: "Identity overrides", Value: identityOverrides(feature.NumIdentityOverrides)},
@@ -168,7 +195,7 @@ func lifecycleOrDash(stage string) string {
168195

169196
func identityOverrides(n *int) string {
170197
if n == nil {
171-
return "-" // Edge/Dynamo projects do not report this
198+
return "0" // Edge/Dynamo projects do not report a count
172199
}
173200
return strconv.Itoa(*n)
174201
}

0 commit comments

Comments
 (0)