@skyhook-io/k8s-ui 1.10.4 → 1.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +5 -5
  2. package/src/components/applications/ApplicationDetail.test.tsx +46 -0
  3. package/src/components/applications/ApplicationDetail.tsx +14 -15
  4. package/src/components/topology/K8sResourceNode.tsx +5 -0
  5. package/src/components/topology/TopologyGraph.tsx +85 -5
  6. package/src/components/trace/ReachabilityGraph.tsx +503 -0
  7. package/src/components/trace/ReachabilityView.tsx +972 -0
  8. package/src/components/trace/TracePanel.test.ts +172 -0
  9. package/src/components/trace/TracePanel.tsx +558 -0
  10. package/src/components/trace/TraceSummary.test.ts +213 -0
  11. package/src/components/trace/TraceSummary.tsx +168 -0
  12. package/src/components/trace/inClusterSummary.test.ts +98 -0
  13. package/src/components/trace/inClusterSummary.ts +81 -0
  14. package/src/components/trace/index.ts +19 -0
  15. package/src/components/trace/podReach.test.ts +71 -0
  16. package/src/components/trace/podReach.ts +37 -0
  17. package/src/components/trace/probe-display.test.ts +107 -0
  18. package/src/components/trace/probe-display.ts +37 -0
  19. package/src/components/trace/reachFixtures.ts +261 -0
  20. package/src/components/trace/reachGraphModel.test.ts +1407 -0
  21. package/src/components/trace/reachGraphModel.ts +1521 -0
  22. package/src/components/trace/reachInspector.test.ts +669 -0
  23. package/src/components/trace/reachInspector.ts +667 -0
  24. package/src/components/trace/reachMarks.test.ts +667 -0
  25. package/src/components/trace/reachMarks.ts +579 -0
  26. package/src/components/trace/reachOrigins.test.ts +392 -0
  27. package/src/components/trace/reachOrigins.ts +437 -0
  28. package/src/components/trace/reachVerdict.test.ts +537 -0
  29. package/src/components/trace/reachVerdict.ts +601 -0
  30. package/src/components/trace/traceFingerprint.test.ts +214 -0
  31. package/src/components/trace/traceFingerprint.ts +122 -0
  32. package/src/components/trace/traceToSubgraph.test.ts +623 -0
  33. package/src/components/trace/traceToSubgraph.ts +463 -0
  34. package/src/components/trace/types.ts +389 -0
  35. package/src/components/ui/InClusterConsentDialog.test.tsx +83 -0
  36. package/src/components/ui/InClusterConsentDialog.tsx +141 -0
  37. package/src/components/ui/index.ts +1 -0
  38. package/src/components/workload/WorkloadView.tsx +58 -5
  39. package/src/components/workload/index.ts +1 -1
  40. package/src/components/workload/reachabilityTab.test.ts +47 -0
  41. package/src/index.ts +5 -0
  42. package/src/theme/components.css +38 -0
  43. package/src/theme/variables.css +5 -0
  44. package/src/types/core.ts +8 -0
  45. package/src/utils/application-history.test.ts +1 -1
  46. package/src/utils/applications.test.ts +63 -1
  47. package/src/utils/applications.ts +27 -0
  48. package/src/utils/inClusterConsent.test.ts +109 -0
  49. package/src/utils/inClusterConsent.ts +72 -0
  50. package/src/utils/index.ts +1 -0
  51. package/src/utils/navigation.test.ts +61 -0
  52. package/src/utils/navigation.ts +11 -9
  53. package/src/utils/pluralize.test.ts +80 -1
  54. package/src/utils/pluralize.ts +34 -0
@@ -1,5 +1,5 @@
1
1
  import type { SelectedResource, ResourceRef, APIResource } from '../types/core'
2
- import { englishPlural } from './pluralize'
2
+ import { englishPlural, englishSingular, isEnglishPlural } from './pluralize'
3
3
 
4
4
  /**
5
5
  * Canonical callback type for navigating to a resource.
@@ -103,6 +103,15 @@ export function kindToPlural(kind: string): string {
103
103
  }
104
104
  if (aliases[kindLower]) return aliases[kindLower]
105
105
 
106
+ // Keep the idempotence contract alive for CRD plurals that neither map knows
107
+ // yet: the discovery map arrives one round-trip after a cold direct-URL load,
108
+ // and English-pluralizing an already-plural slug yields `scheduleses`. An
109
+ // all-lowercase input is an API resource name rather than a Kind (Kubernetes
110
+ // Kinds are PascalCase), and isEnglishPlural separates a real plural from a
111
+ // singular that merely ends in 's' — `ingress` and `nodeclass` must still be
112
+ // pluralized, `schedules` and `validatingpolicies` must not.
113
+ if (kind === kindLower && isEnglishPlural(kindLower)) return kindLower
114
+
106
115
  // Fallback: English pluralization rules (shared with pluralize() in
107
116
  // utils/pluralize.ts so a rule change updates both call paths).
108
117
  return englishPlural(kindLower)
@@ -125,14 +134,7 @@ export function pluralToKind(plural: string): string {
125
134
  }
126
135
 
127
136
  // Fallback: basic de-pluralization + capitalize first letter
128
- let singular = lower
129
- if (singular.endsWith('ies')) {
130
- singular = singular.slice(0, -3) + 'y'
131
- } else if (singular.endsWith('ses') || singular.endsWith('xes') || singular.endsWith('ches') || singular.endsWith('shes')) {
132
- singular = singular.slice(0, -2)
133
- } else if (singular.endsWith('s')) {
134
- singular = singular.slice(0, -1)
135
- }
137
+ const singular = englishSingular(lower)
136
138
  return singular.charAt(0).toUpperCase() + singular.slice(1)
137
139
  }
138
140
 
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest'
2
- import { englishPlural, pluralNoun, pluralize } from './pluralize'
2
+ import { englishPlural, englishSingular, isEnglishPlural, pluralNoun, pluralize } from './pluralize'
3
3
 
4
4
  describe('englishPlural', () => {
5
5
  describe('default +s', () => {
@@ -96,3 +96,82 @@ describe('pluralize', () => {
96
96
  expect(pluralize(2, 'box')).toBe('2 boxes')
97
97
  })
98
98
  })
99
+
100
+ describe('englishSingular', () => {
101
+ it('inverts englishPlural for regular plurals', () => {
102
+ expect(englishSingular('clusters')).toBe('cluster')
103
+ expect(englishSingular('policies')).toBe('policy')
104
+ expect(englishSingular('classes')).toBe('class')
105
+ expect(englishSingular('boxes')).toBe('box')
106
+ expect(englishSingular('batches')).toBe('batch')
107
+ })
108
+
109
+ it('leaves nouns with no plural ending alone', () => {
110
+ expect(englishSingular('cluster')).toBe('cluster')
111
+ expect(englishSingular('policy')).toBe('policy')
112
+ })
113
+
114
+ // The `-es` strip over-shortens a `*se` singular ('leases' → 'leas', not
115
+ // 'lease'), but englishPlural's `s → +es` rule inverts it exactly, so the
116
+ // round trip these two functions are used for still holds. Only the round
117
+ // trip is contractual — the intermediate stem is not.
118
+ it('over-strips *se stems but stays round-trip exact', () => {
119
+ expect(englishSingular('leases')).toBe('leas')
120
+ expect(englishPlural('leas')).toBe('leases')
121
+ expect(englishSingular('helmreleases')).toBe('helmreleas')
122
+ expect(englishPlural('helmreleas')).toBe('helmreleases')
123
+ })
124
+ })
125
+
126
+ describe('isEnglishPlural', () => {
127
+ it('recognizes regular plurals', () => {
128
+ expect(isEnglishPlural('schedules')).toBe(true)
129
+ expect(isEnglishPlural('validatingpolicies')).toBe(true)
130
+ expect(isEnglishPlural('virtualservices')).toBe(true)
131
+ expect(isEnglishPlural('gatewayclasses')).toBe(true)
132
+ expect(isEnglishPlural('endpoints')).toBe(true)
133
+ })
134
+
135
+ // Plurals whose singular ends in `se`. The stem englishSingular produces is
136
+ // wrong, but the round trip is not — see the englishSingular case above.
137
+ it('recognizes plurals of *se singulars', () => {
138
+ expect(isEnglishPlural('leases')).toBe(true)
139
+ expect(isEnglishPlural('helmreleases')).toBe(true)
140
+ expect(isEnglishPlural('releases')).toBe(true)
141
+ expect(isEnglishPlural('licenses')).toBe(true)
142
+ expect(isEnglishPlural('databases')).toBe(true)
143
+ })
144
+
145
+ // KNOWN LIMIT. A singular ending in a lone `s` whose stem doesn't end in
146
+ // s/x/ch/sh is indistinguishable from a plural by English rules alone
147
+ // ('status' → 'statu' → 'status' round-trips exactly like 'clusters' →
148
+ // 'cluster' → 'clusters'). No Kubernetes Kind Radar handles has this shape,
149
+ // and kindToPlural's discovered-map lookup takes over the moment discovery
150
+ // lands, so the mistake is confined to the cold window. Pinned so a future
151
+ // change to these rules surfaces the boundary rather than moving it silently.
152
+ it('cannot distinguish singulars ending in a lone s', () => {
153
+ expect(isEnglishPlural('status')).toBe(true)
154
+ expect(isEnglishPlural('alias')).toBe(true)
155
+ expect(isEnglishPlural('canvas')).toBe(true)
156
+ })
157
+
158
+ // The reason this predicate exists rather than a bare endsWith('s'): these
159
+ // are singular Kubernetes kinds that happen to end in 's'.
160
+ it('rejects singulars that merely end in s', () => {
161
+ expect(isEnglishPlural('ingress')).toBe(false)
162
+ expect(isEnglishPlural('nodeclass')).toBe(false)
163
+ expect(isEnglishPlural('ec2nodeclass')).toBe(false)
164
+ expect(isEnglishPlural('storageclass')).toBe(false)
165
+ })
166
+
167
+ it('rejects singulars with no plural ending', () => {
168
+ expect(isEnglishPlural('cluster')).toBe(false)
169
+ expect(isEnglishPlural('validatingpolicy')).toBe(false)
170
+ })
171
+
172
+ it('agrees with englishPlural on every round trip', () => {
173
+ for (const singular of ['cluster', 'policy', 'class', 'box', 'batch', 'schedule', 'service']) {
174
+ expect(isEnglishPlural(englishPlural(singular))).toBe(true)
175
+ }
176
+ })
177
+ })
@@ -30,6 +30,40 @@ export function englishPlural(noun: string): string {
30
30
  return noun + 's';
31
31
  }
32
32
 
33
+ /**
34
+ * Undo englishPlural's regular rules.
35
+ *
36
+ * englishSingular('classes') → 'class'
37
+ * englishSingular('policies') → 'policy'
38
+ * englishSingular('clusters') → 'cluster'
39
+ *
40
+ * Lossy by construction — a noun that is already singular comes back mangled
41
+ * ('ingress' → 'ingres'). Use isEnglishPlural to find out which case you have.
42
+ */
43
+ export function englishSingular(noun: string): string {
44
+ const lower = noun.toLowerCase();
45
+ if (lower.endsWith('ies')) return noun.slice(0, -3) + 'y';
46
+ if (lower.endsWith('ses') || lower.endsWith('xes') || lower.endsWith('ches') || lower.endsWith('shes')) {
47
+ return noun.slice(0, -2);
48
+ }
49
+ if (lower.endsWith('s')) return noun.slice(0, -1);
50
+ return noun;
51
+ }
52
+
53
+ /**
54
+ * Whether a noun is already the regular English plural of its own singular —
55
+ * i.e. whether englishPlural would reproduce it. Distinguishes a plural from a
56
+ * singular that merely ends in 's':
57
+ *
58
+ * isEnglishPlural('schedules') → true ('schedule' → 'schedules')
59
+ * isEnglishPlural('policies') → true ('policy' → 'policies')
60
+ * isEnglishPlural('ingress') → false ('ingres' → 'ingreses')
61
+ * isEnglishPlural('nodeclass') → false ('nodeclas' → 'nodeclases')
62
+ */
63
+ export function isEnglishPlural(noun: string): boolean {
64
+ return englishPlural(englishSingular(noun)) === noun;
65
+ }
66
+
33
67
  /**
34
68
  * Count-aware plural noun. Returns the singular when n===1, else the
35
69
  * plural form (passed-in or derived via englishPlural).