@spinnaker/kubernetes 2026.2.2 → 2026.3.0

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 (158) hide show
  1. package/dist/index.d.ts +1 -2
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/instance/details/KubernetesInstanceDetails.d.ts +54 -0
  5. package/dist/instance/details/kubernetesInstanceDetails.utils.d.ts +13 -0
  6. package/dist/instance/index.d.ts +1 -1
  7. package/dist/kubernetes.module.d.ts +5 -1
  8. package/dist/loadBalancer/transformer.d.ts +1 -1
  9. package/dist/manifest/AnnotationCustomSections.d.ts +3 -2
  10. package/dist/manifest/ManifestKindSearch.d.ts +1 -1
  11. package/dist/manifest/delete/Delete.d.ts +0 -1
  12. package/dist/manifest/delete/DeleteModal.d.ts +1 -1
  13. package/dist/manifest/delete/delete.types.d.ts +4 -0
  14. package/dist/manifest/index.d.ts +3 -6
  15. package/dist/manifest/manifest.service.d.ts +5 -0
  16. package/dist/manifest/manifestCommandBuilder.service.d.ts +1 -1
  17. package/dist/manifest/rollout/PauseRollout.d.ts +0 -1
  18. package/dist/manifest/rollout/ResumeRollout.d.ts +0 -1
  19. package/dist/manifest/rollout/RollingRestart.d.ts +4 -4
  20. package/dist/manifest/rollout/UndoRollout.d.ts +0 -1
  21. package/dist/manifest/scale/Scale.d.ts +1 -2
  22. package/dist/manifest/scale/ScaleModal.d.ts +3 -3
  23. package/dist/manifest/scale/ScaleSettingsForm.d.ts +7 -1
  24. package/dist/manifest/selector/ManifestSelector.d.ts +1 -1
  25. package/dist/manifest/traffic/ManifestTrafficService.d.ts +2 -2
  26. package/dist/pipelines/stages/deleteManifest/DeleteManifestOptionsForm.d.ts +1 -1
  27. package/dist/pipelines/stages/deployManifest/manifestStatus/DeployStatus.d.ts +1 -0
  28. package/dist/pipelines/stages/deployManifest/manifestStatus/ManifestDetailsLink.d.ts +6 -3
  29. package/dist/pipelines/stages/traffic/disableManifest.stage.d.ts +2 -1
  30. package/dist/pipelines/stages/traffic/enableManifest.stage.d.ts +2 -1
  31. package/dist/rawResource/index.d.ts +3 -1
  32. package/dist/rawResource/rawResource.dataSource.d.ts +1 -1
  33. package/dist/rawResource/rawResource.states.d.ts +4 -1
  34. package/dist/resources/resources.state.d.ts +2 -1
  35. package/dist/securityGroup/details/KubernetesSecurityGroupDetails.d.ts +49 -0
  36. package/dist/securityGroup/index.d.ts +3 -1
  37. package/dist/securityGroup/transformer.d.ts +4 -1
  38. package/dist/serverGroup/serverGroupCommandBuilder.d.ts +1 -1
  39. package/dist/serverGroup/serverGroupTransformer.d.ts +1 -1
  40. package/dist/serverGroupManager/details/ServerGroupManagerDetails.d.ts +7 -3
  41. package/package.json +3 -12
  42. package/src/help/kubernetes.help.ts +2 -0
  43. package/src/index.ts +2 -2
  44. package/src/instance/details/KubernetesInstanceDetails.spec.tsx +451 -0
  45. package/src/instance/details/KubernetesInstanceDetails.tsx +342 -0
  46. package/src/instance/details/kubernetesInstanceDetails.utils.ts +77 -0
  47. package/src/instance/index.ts +1 -1
  48. package/src/kubernetes.module.spec.ts +25 -0
  49. package/src/kubernetes.module.ts +23 -87
  50. package/src/loadBalancer/details/KubernetesLoadBalancerActions.tsx +30 -35
  51. package/src/loadBalancer/details/sections/LoadBalancerAnnotationCustomSection.tsx +2 -3
  52. package/src/loadBalancer/transformer.ts +1 -1
  53. package/src/manifest/AnnotationCustomSections.spec.tsx +178 -0
  54. package/src/manifest/AnnotationCustomSections.tsx +179 -2
  55. package/src/manifest/ManifestKindSearch.tsx +1 -1
  56. package/src/manifest/delete/Delete.tsx +0 -9
  57. package/src/manifest/delete/DeleteModal.tsx +69 -72
  58. package/src/manifest/delete/delete.types.ts +4 -0
  59. package/src/manifest/index.ts +3 -6
  60. package/src/manifest/manifest.service.ts +18 -1
  61. package/src/manifest/manifestCommandBuilder.service.spec.ts +41 -0
  62. package/src/manifest/manifestCommandBuilder.service.ts +45 -59
  63. package/src/manifest/rollout/PauseRollout.tsx +1 -9
  64. package/src/manifest/rollout/ResumeRollout.tsx +2 -10
  65. package/src/manifest/rollout/RollingRestart.tsx +16 -22
  66. package/src/manifest/rollout/UndoRollout.tsx +1 -10
  67. package/src/manifest/rollout/UndoRolloutModal.tsx +5 -1
  68. package/src/manifest/scale/Scale.tsx +12 -11
  69. package/src/manifest/scale/ScaleModal.tsx +43 -31
  70. package/src/manifest/scale/ScaleSettingsForm.tsx +8 -1
  71. package/src/manifest/selector/ManifestSelector.spec.tsx +89 -46
  72. package/src/manifest/selector/ManifestSelector.tsx +4 -5
  73. package/src/manifest/traffic/ManifestTrafficService.ts +2 -2
  74. package/src/manifest/wizard/ManifestWizard.tsx +1 -1
  75. package/src/pipelines/stages/deleteManifest/DeleteManifestOptionsForm.tsx +1 -1
  76. package/src/pipelines/stages/deleteManifest/DeleteManifestStageConfig.tsx +1 -1
  77. package/src/pipelines/stages/deployManifest/DeployManifestStageForm.tsx +16 -0
  78. package/src/pipelines/stages/deployManifest/ManifestCopier.spec.ts +52 -77
  79. package/src/pipelines/stages/deployManifest/ManifestDeploymentOptions.spec.tsx +2 -0
  80. package/src/pipelines/stages/deployManifest/manifestStatus/DeployStatus.tsx +39 -4
  81. package/src/pipelines/stages/deployManifest/manifestStatus/ManifestDetailsLink.spec.tsx +35 -0
  82. package/src/pipelines/stages/deployManifest/manifestStatus/ManifestDetailsLink.tsx +10 -5
  83. package/src/pipelines/stages/traffic/ManifestTrafficStageRegistration.spec.ts +34 -0
  84. package/src/pipelines/stages/traffic/disableManifest.stage.ts +17 -19
  85. package/src/pipelines/stages/traffic/enableManifest.stage.ts +17 -19
  86. package/src/rawResource/index.ts +18 -1
  87. package/src/rawResource/rawResource.dataSource.ts +5 -8
  88. package/src/rawResource/rawResource.states.ts +50 -51
  89. package/src/resources/resources.state.spec.ts +138 -0
  90. package/src/resources/resources.state.ts +30 -35
  91. package/src/securityGroup/details/KubernetesSecurityGroupDetails.spec.tsx +292 -0
  92. package/src/securityGroup/details/KubernetesSecurityGroupDetails.tsx +304 -0
  93. package/src/securityGroup/index.ts +3 -1
  94. package/src/securityGroup/transformer.ts +2 -10
  95. package/src/serverGroup/details/KubernetesServerGroupActions.tsx +31 -61
  96. package/src/serverGroup/details/kubernetesServerGroupDetailsGetter.ts +1 -1
  97. package/src/serverGroup/details/sections/ServerGroupAnnotationCustomSection.tsx +2 -3
  98. package/src/serverGroup/serverGroupCommandBuilder.ts +1 -1
  99. package/src/serverGroup/serverGroupTransformer.ts +1 -1
  100. package/src/serverGroupManager/details/ServerGroupManagerActions.tsx +48 -128
  101. package/src/serverGroupManager/details/ServerGroupManagerDetails.spec.ts +12 -0
  102. package/src/serverGroupManager/details/ServerGroupManagerDetails.tsx +19 -8
  103. package/src/serverGroupManager/details/sections/ServerGroupManagerAnnotationCustomSection.tsx +2 -3
  104. package/src/serverGroupManager/details/useKubernetesServerGroupManagerDetails.spec.tsx +112 -0
  105. package/src/serverGroupManager/details/useKubernetesServerGroupManagerDetails.tsx +50 -29
  106. package/dist/instance/details/details.controller.d.ts +0 -1
  107. package/dist/manifest/annotationCustomSections.component.d.ts +0 -3
  108. package/dist/manifest/delete/delete.controller.d.ts +0 -12
  109. package/dist/manifest/delete/deleteOptionsForm.component.d.ts +0 -1
  110. package/dist/manifest/editor/json/jsonEditor.component.d.ts +0 -1
  111. package/dist/manifest/manifestEvents.component.d.ts +0 -1
  112. package/dist/manifest/manifestImageDetails.component.d.ts +0 -1
  113. package/dist/manifest/manifestLabels.component.d.ts +0 -1
  114. package/dist/manifest/manifestQos.component.d.ts +0 -1
  115. package/dist/manifest/manifestResources.component.d.ts +0 -1
  116. package/dist/manifest/rollout/pause.controller.d.ts +0 -1
  117. package/dist/manifest/rollout/resume.controller.d.ts +0 -1
  118. package/dist/manifest/rollout/undo.controller.d.ts +0 -5
  119. package/dist/manifest/scale/scale.controller.d.ts +0 -8
  120. package/dist/manifest/scale/scaleSettingsForm.component.d.ts +0 -1
  121. package/dist/manifest/selector/selector.component.d.ts +0 -1
  122. package/dist/manifest/status/condition.component.d.ts +0 -1
  123. package/dist/manifest/status/status.component.d.ts +0 -1
  124. package/dist/rawResource/rawResource.module.d.ts +0 -1
  125. package/dist/reactShims/index.d.ts +0 -1
  126. package/dist/reactShims/kubernetes.react.injector.d.ts +0 -11
  127. package/dist/reactShims/kubernetes.react.module.d.ts +0 -1
  128. package/dist/securityGroup/details/details.controller.d.ts +0 -1
  129. package/src/instance/details/details.controller.ts +0 -186
  130. package/src/instance/details/details.html +0 -80
  131. package/src/manifest/annotationCustomSections.component.ts +0 -114
  132. package/src/manifest/delete/delete.controller.ts +0 -80
  133. package/src/manifest/delete/delete.html +0 -42
  134. package/src/manifest/delete/deleteOptionsForm.component.ts +0 -52
  135. package/src/manifest/editor/json/jsonEditor.component.ts +0 -12
  136. package/src/manifest/manifestEvents.component.ts +0 -12
  137. package/src/manifest/manifestImageDetails.component.ts +0 -12
  138. package/src/manifest/manifestLabels.component.ts +0 -12
  139. package/src/manifest/manifestQos.component.ts +0 -12
  140. package/src/manifest/manifestResources.component.ts +0 -12
  141. package/src/manifest/rollout/pause.controller.ts +0 -66
  142. package/src/manifest/rollout/pause.html +0 -28
  143. package/src/manifest/rollout/resume.controller.ts +0 -66
  144. package/src/manifest/rollout/resume.html +0 -28
  145. package/src/manifest/rollout/undo.controller.ts +0 -74
  146. package/src/manifest/rollout/undo.html +0 -44
  147. package/src/manifest/scale/scale.controller.ts +0 -75
  148. package/src/manifest/scale/scale.html +0 -32
  149. package/src/manifest/scale/scaleSettingsForm.component.ts +0 -11
  150. package/src/manifest/selector/selector.component.ts +0 -17
  151. package/src/manifest/status/condition.component.ts +0 -12
  152. package/src/manifest/status/status.component.ts +0 -11
  153. package/src/rawResource/rawResource.module.ts +0 -8
  154. package/src/reactShims/index.ts +0 -1
  155. package/src/reactShims/kubernetes.react.injector.ts +0 -30
  156. package/src/reactShims/kubernetes.react.module.ts +0 -12
  157. package/src/securityGroup/details/details.controller.ts +0 -118
  158. package/src/securityGroup/details/details.html +0 -91
@@ -0,0 +1,451 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import {
5
+ AccountTag,
6
+ CollapsibleSection,
7
+ ConsoleOutputLink,
8
+ InstanceDetailsHeader,
9
+ InstanceLinks,
10
+ InstanceReader,
11
+ ManifestReader,
12
+ RecentHistoryService,
13
+ } from '@spinnaker/core';
14
+
15
+ import type { IKubernetesInstanceDetailsProps } from './KubernetesInstanceDetails';
16
+ import {
17
+ KubernetesInstanceActions,
18
+ KubernetesInstanceDetailsComponent as KubernetesInstanceDetails,
19
+ } from './KubernetesInstanceDetails';
20
+ import { findKubernetesInstanceIdentifier } from './kubernetesInstanceDetails.utils';
21
+ import { AnnotationCustomSections } from '../../manifest/AnnotationCustomSections';
22
+ import { ManifestLabels } from '../../manifest/ManifestLabels';
23
+ import { ManifestQos } from '../../manifest/ManifestQos';
24
+ import { ManifestResources } from '../../manifest/ManifestResources';
25
+ import { ManifestEvents } from '../../pipelines/stages/deployManifest/manifestStatus/ManifestEvents';
26
+ import { ManifestCondition } from '../../manifest';
27
+
28
+ describe('findKubernetesInstanceIdentifier', () => {
29
+ it('finds pod instances under server groups and records server group recent-history data', () => {
30
+ const addRecentHistory = jasmine.createSpy('addRecentHistory');
31
+ const identifier = findKubernetesInstanceIdentifier(
32
+ appWithInfrastructure({
33
+ serverGroups: [
34
+ instanceManager({
35
+ category: 'serverGroup',
36
+ name: 'replicaSet backend-abc123',
37
+ instances: [{ id: 'pod-uid', name: 'pod backend-abc123-def45' }],
38
+ }),
39
+ ],
40
+ }),
41
+ 'pod-uid',
42
+ addRecentHistory,
43
+ );
44
+
45
+ expect(identifier).toEqual({
46
+ account: 'k8s-local',
47
+ id: 'pod-uid',
48
+ name: 'pod backend-abc123-def45',
49
+ namespace: 'dev',
50
+ });
51
+ expect(addRecentHistory).toHaveBeenCalledWith({
52
+ account: 'k8s-local',
53
+ region: 'dev',
54
+ serverGroup: 'replicaSet backend-abc123',
55
+ });
56
+ });
57
+
58
+ it('finds pod instances under load balancers without server group recent-history data', () => {
59
+ const addRecentHistory = jasmine.createSpy('addRecentHistory');
60
+ const identifier = findKubernetesInstanceIdentifier(
61
+ appWithInfrastructure({
62
+ loadBalancers: [
63
+ instanceManager({
64
+ category: 'loadBalancer',
65
+ name: 'service backend',
66
+ instances: [{ id: 'service-pod-uid', name: 'pod backend-from-service' }],
67
+ }),
68
+ ],
69
+ }),
70
+ 'service-pod-uid',
71
+ addRecentHistory,
72
+ );
73
+
74
+ expect(identifier).toEqual({
75
+ account: 'k8s-local',
76
+ id: 'service-pod-uid',
77
+ name: 'pod backend-from-service',
78
+ namespace: 'dev',
79
+ });
80
+ expect(addRecentHistory).toHaveBeenCalledWith({
81
+ account: 'k8s-local',
82
+ region: 'dev',
83
+ });
84
+ });
85
+
86
+ it('finds pod instances under load balancer server groups and records server group recent-history data', () => {
87
+ const addRecentHistory = jasmine.createSpy('addRecentHistory');
88
+ const identifier = findKubernetesInstanceIdentifier(
89
+ appWithInfrastructure({
90
+ loadBalancers: [
91
+ {
92
+ serverGroups: [
93
+ instanceManager({
94
+ category: 'serverGroup',
95
+ name: 'replicaSet backend-from-service',
96
+ instances: [{ id: 'nested-pod-uid', name: 'pod backend-from-nested-service' }],
97
+ }),
98
+ ],
99
+ },
100
+ ],
101
+ }),
102
+ 'nested-pod-uid',
103
+ addRecentHistory,
104
+ );
105
+
106
+ expect(identifier).toEqual({
107
+ account: 'k8s-local',
108
+ id: 'nested-pod-uid',
109
+ name: 'pod backend-from-nested-service',
110
+ namespace: 'dev',
111
+ });
112
+ expect(addRecentHistory).toHaveBeenCalledWith({
113
+ account: 'k8s-local',
114
+ region: 'dev',
115
+ serverGroup: 'replicaSet backend-from-service',
116
+ });
117
+ });
118
+
119
+ it('returns null for missing instances without recording recent-history data', () => {
120
+ const addRecentHistory = jasmine.createSpy('addRecentHistory');
121
+
122
+ expect(
123
+ findKubernetesInstanceIdentifier(
124
+ appWithInfrastructure({
125
+ serverGroups: [instanceManager({ instances: [{ id: 'other-pod', name: 'pod other' }] })],
126
+ }),
127
+ 'missing-pod',
128
+ addRecentHistory,
129
+ ),
130
+ ).toBeNull();
131
+ expect(addRecentHistory).not.toHaveBeenCalled();
132
+ });
133
+ });
134
+
135
+ describe('<KubernetesInstanceDetails />', () => {
136
+ let props: IKubernetesInstanceDetailsProps;
137
+
138
+ beforeEach(() => {
139
+ props = {
140
+ app: appWithInfrastructure({
141
+ serverGroups: [
142
+ instanceManager({
143
+ category: 'serverGroup',
144
+ name: 'replicaSet backend-abc123',
145
+ instances: [{ id: 'pod-uid', name: 'pod backend-abc123-def45' }],
146
+ }),
147
+ ],
148
+ }),
149
+ environment: 'test',
150
+ instance: { instanceId: 'pod-uid' },
151
+ moniker: { app: 'kubernetesapp', cluster: 'deployment backend' },
152
+ } as IKubernetesInstanceDetailsProps;
153
+
154
+ spyOn(InstanceReader, 'getInstanceDetails').and.returnValue(Promise.resolve(instanceDetails()) as any);
155
+ spyOn(ManifestReader, 'getManifest').and.returnValue(Promise.resolve(manifestDetails()) as any);
156
+ spyOn(RecentHistoryService, 'addExtraDataToLatest');
157
+ });
158
+
159
+ it('loads instance and manifest details before rendering the React sections', async () => {
160
+ const component = shallow(<KubernetesInstanceDetails {...props} />);
161
+
162
+ await settle();
163
+ component.update();
164
+
165
+ expect(InstanceReader.getInstanceDetails).toHaveBeenCalledWith('k8s-local', 'dev', 'pod backend-abc123-def45');
166
+ expect(ManifestReader.getManifest).toHaveBeenCalledWith('k8s-local', 'dev', 'pod backend-abc123-def45');
167
+ expect(RecentHistoryService.addExtraDataToLatest).toHaveBeenCalledWith('instances', {
168
+ account: 'k8s-local',
169
+ region: 'dev',
170
+ serverGroup: 'replicaSet backend-abc123',
171
+ });
172
+ expect(component.find(InstanceDetailsHeader).prop('instanceId')).toBe('backend-abc123-def45');
173
+ expect(component.find(CollapsibleSection).map((section) => section.prop('heading'))).toEqual([
174
+ 'Information',
175
+ 'Status',
176
+ 'Events',
177
+ 'Resources',
178
+ 'Labels',
179
+ ]);
180
+ expect(component.find(AccountTag).prop('account')).toBe('k8s-local');
181
+ expect(component.find(ManifestQos).prop('manifest')).toEqual(manifestDetails().manifest);
182
+ expect(component.find(ManifestCondition).length).toBe(1);
183
+ expect(component.find(ManifestEvents).prop('manifest')).toEqual(manifestDetails());
184
+ expect(component.find(ManifestResources).prop('metrics')).toEqual(manifestDetails().metrics);
185
+ expect(component.find(ManifestLabels).prop('manifest')).toEqual(manifestDetails().manifest);
186
+ expect(component.find(AnnotationCustomSections).prop('manifest')).toEqual(manifestDetails().manifest);
187
+ expect(component.find(AnnotationCustomSections).prop('resource')).toEqual(
188
+ jasmine.objectContaining({
189
+ account: 'k8s-local',
190
+ name: 'pod backend-abc123-def45',
191
+ provider: 'kubernetes',
192
+ }),
193
+ );
194
+ expect(component.find(InstanceLinks).prop('application')).toBe(props.app);
195
+ expect(component.find(InstanceLinks).prop('environment')).toBe('test');
196
+ expect(component.find(InstanceLinks).prop('address')).toBe('backend.example.com');
197
+ expect(component.find(ConsoleOutputLink).prop('usesMultiOutput')).toBe(true);
198
+ expect(component.find(KubernetesInstanceActions).prop('app')).toBe(props.app);
199
+ expect(component.find(KubernetesInstanceActions).prop('manifest')).toEqual(manifestDetails());
200
+ expect(component.find(KubernetesInstanceActions).prop('instance')).toEqual(
201
+ jasmine.objectContaining({
202
+ account: 'k8s-local',
203
+ name: 'pod backend-abc123-def45',
204
+ provider: 'kubernetes',
205
+ }),
206
+ );
207
+ expect(informationSectionText(component)).not.toContain('Node IP');
208
+ expect(informationSectionText(component)).not.toContain('Pod IP');
209
+ });
210
+
211
+ it('auto-closes when the instance cannot be found in application infrastructure', async () => {
212
+ const autoClose = jasmine.createSpy('autoClose');
213
+ const component = shallow(
214
+ <KubernetesInstanceDetails
215
+ {...props}
216
+ app={appWithInfrastructure({ serverGroups: [], loadBalancers: [] })}
217
+ autoClose={autoClose}
218
+ />,
219
+ );
220
+
221
+ await settle();
222
+ component.update();
223
+
224
+ expect(autoClose).toHaveBeenCalled();
225
+ expect(InstanceReader.getInstanceDetails).not.toHaveBeenCalled();
226
+ expect(ManifestReader.getManifest).not.toHaveBeenCalled();
227
+ });
228
+
229
+ it('replaces missing instance details through the injected state service', () => {
230
+ const stateService = { go: jasmine.createSpy('go'), params: {} };
231
+ const component = new KubernetesInstanceDetails({
232
+ ...props,
233
+ router: {},
234
+ stateParams: {},
235
+ stateService,
236
+ } as any);
237
+
238
+ (component as any).autoClose();
239
+
240
+ expect(stateService.params.allowModalToStayOpen).toBe(true);
241
+ expect(stateService.go).toHaveBeenCalledWith('^', null, { location: 'replace' });
242
+ });
243
+
244
+ it('waits for application data before loading changed instance props', async () => {
245
+ const autoClose = jasmine.createSpy('autoClose');
246
+ const ready = deferred<void>();
247
+ const serverGroups: any[] = [];
248
+ const app = appWithInfrastructure({ serverGroups });
249
+ app.ready = () => ready.promise;
250
+ const component = shallow(<KubernetesInstanceDetails {...props} app={app} autoClose={autoClose} />);
251
+
252
+ component.setProps({ instance: { instanceId: 'new-pod-uid' } });
253
+ await settle();
254
+
255
+ expect(autoClose).not.toHaveBeenCalled();
256
+ expect(InstanceReader.getInstanceDetails).not.toHaveBeenCalled();
257
+ expect(ManifestReader.getManifest).not.toHaveBeenCalled();
258
+
259
+ serverGroups.push(
260
+ instanceManager({
261
+ instances: [{ id: 'new-pod-uid', name: 'pod backend-new' }],
262
+ }),
263
+ );
264
+ ready.resolve();
265
+ await settle();
266
+ component.update();
267
+
268
+ expect(InstanceReader.getInstanceDetails).toHaveBeenCalledWith('k8s-local', 'dev', 'pod backend-new');
269
+ });
270
+
271
+ it('keeps the newer pod details when an older load resolves last', async () => {
272
+ const oldInstance = deferred<any>();
273
+ const oldManifest = deferred<any>();
274
+ const newInstance = deferred<any>();
275
+ const newManifest = deferred<any>();
276
+ props = {
277
+ ...props,
278
+ app: appWithInfrastructure({
279
+ serverGroups: [
280
+ instanceManager({
281
+ instances: [
282
+ { id: 'pod-uid', name: 'pod backend-old' },
283
+ { id: 'new-pod-uid', name: 'pod backend-new' },
284
+ ],
285
+ }),
286
+ ],
287
+ }),
288
+ };
289
+
290
+ (InstanceReader.getInstanceDetails as jasmine.Spy).and.callFake(
291
+ (_account: string, _namespace: string, name: string) =>
292
+ name === 'pod backend-new' ? newInstance.promise : oldInstance.promise,
293
+ );
294
+ (ManifestReader.getManifest as jasmine.Spy).and.callFake((_account: string, _namespace: string, name: string) =>
295
+ name === 'pod backend-new' ? newManifest.promise : oldManifest.promise,
296
+ );
297
+
298
+ const component = shallow(<KubernetesInstanceDetails {...props} />);
299
+
300
+ await settle();
301
+ component.setProps({ instance: { instanceId: 'new-pod-uid' } });
302
+
303
+ newInstance.resolve(instanceDetails({ displayName: 'backend-new', humanReadableName: 'pod backend-new' }));
304
+ newManifest.resolve(manifestDetails());
305
+ await settle();
306
+ component.update();
307
+
308
+ expect(component.find(InstanceDetailsHeader).prop('instanceId')).toBe('backend-new');
309
+
310
+ oldInstance.resolve(instanceDetails({ displayName: 'backend-old', humanReadableName: 'pod backend-old' }));
311
+ oldManifest.resolve(manifestDetails());
312
+ await settle();
313
+ component.update();
314
+
315
+ expect(component.find(InstanceDetailsHeader).prop('instanceId')).toBe('backend-new');
316
+ });
317
+
318
+ it('ignores stale load failures after a newer pod has rendered', async () => {
319
+ const autoClose = jasmine.createSpy('autoClose');
320
+ const oldInstance = deferred<any>();
321
+ const oldManifest = deferred<any>();
322
+ const newInstance = deferred<any>();
323
+ const newManifest = deferred<any>();
324
+ props = {
325
+ ...props,
326
+ autoClose,
327
+ app: appWithInfrastructure({
328
+ serverGroups: [
329
+ instanceManager({
330
+ instances: [
331
+ { id: 'pod-uid', name: 'pod backend-old' },
332
+ { id: 'new-pod-uid', name: 'pod backend-new' },
333
+ ],
334
+ }),
335
+ ],
336
+ }),
337
+ };
338
+
339
+ (InstanceReader.getInstanceDetails as jasmine.Spy).and.callFake(
340
+ (_account: string, _namespace: string, name: string) =>
341
+ name === 'pod backend-new' ? newInstance.promise : oldInstance.promise,
342
+ );
343
+ (ManifestReader.getManifest as jasmine.Spy).and.callFake((_account: string, _namespace: string, name: string) =>
344
+ name === 'pod backend-new' ? newManifest.promise : oldManifest.promise,
345
+ );
346
+
347
+ const component = shallow(<KubernetesInstanceDetails {...props} />);
348
+
349
+ await settle();
350
+ component.setProps({ instance: { instanceId: 'new-pod-uid' } });
351
+
352
+ oldInstance.resolve(instanceDetails({ displayName: 'backend-old', humanReadableName: 'pod backend-old' }));
353
+ newInstance.resolve(instanceDetails({ displayName: 'backend-new', humanReadableName: 'pod backend-new' }));
354
+ newManifest.resolve(manifestDetails());
355
+ await settle();
356
+ component.update();
357
+
358
+ oldManifest.reject(new Error('stale load failed'));
359
+ await settle();
360
+ component.update();
361
+
362
+ expect(autoClose).not.toHaveBeenCalled();
363
+ expect(component.find(InstanceDetailsHeader).prop('instanceId')).toBe('backend-new');
364
+ });
365
+ });
366
+
367
+ const settle = () => new Promise((resolve) => setTimeout(resolve));
368
+
369
+ const informationSectionText = (component: any) =>
370
+ shallow(<div>{component.find(CollapsibleSection).at(0).prop('children')}</div>).text();
371
+
372
+ function deferred<T>() {
373
+ let resolve!: (value: T | PromiseLike<T>) => void;
374
+ let reject!: (reason?: any) => void;
375
+ const promise = new Promise<T>((resolvePromise, rejectPromise) => {
376
+ resolve = resolvePromise;
377
+ reject = rejectPromise;
378
+ });
379
+
380
+ return { promise, resolve, reject };
381
+ }
382
+
383
+ const instanceManager = (overrides: any = {}) => ({
384
+ account: 'k8s-local',
385
+ region: 'dev',
386
+ category: 'serverGroup',
387
+ name: 'replicaSet backend',
388
+ instances: [] as any[],
389
+ ...overrides,
390
+ });
391
+
392
+ const appWithInfrastructure = ({
393
+ serverGroups = [],
394
+ loadBalancers = [],
395
+ }: {
396
+ serverGroups?: any[];
397
+ loadBalancers?: any[];
398
+ }) =>
399
+ ({
400
+ isStandalone: false,
401
+ ready: () => Promise.resolve(),
402
+ onRefresh: () => () => null,
403
+ getDataSource: (key: string) => ({
404
+ data: key === 'serverGroups' ? serverGroups : loadBalancers,
405
+ }),
406
+ serverGroups: {
407
+ refresh: jasmine.createSpy('refreshServerGroups'),
408
+ },
409
+ } as any);
410
+
411
+ const instanceDetails = (overrides: any = {}) =>
412
+ ({
413
+ account: 'k8s-local',
414
+ apiVersion: 'v1',
415
+ cloudProvider: 'kubernetes',
416
+ createdTime: 1753320449000,
417
+ displayName: 'backend-abc123-def45',
418
+ healthState: 'Up',
419
+ humanReadableName: 'pod backend-abc123-def45',
420
+ kind: 'pod',
421
+ moniker: { app: 'kubernetesapp', cluster: 'deployment backend' },
422
+ namespace: 'dev',
423
+ publicDnsName: 'backend.example.com',
424
+ zone: 'dev',
425
+ ...overrides,
426
+ } as any);
427
+
428
+ const manifestDetails = () =>
429
+ ({
430
+ account: 'k8s-local',
431
+ metrics: [{ containerName: 'backend', metrics: { 'CPU(cores)': '1', 'MEMORY(bytes)': '1Gi' } }],
432
+ manifest: {
433
+ metadata: {
434
+ labels: {
435
+ app: 'backend',
436
+ },
437
+ name: 'backend-abc123-def45',
438
+ },
439
+ spec: {
440
+ containers: [{ name: 'backend' }],
441
+ nodeName: 'worker.dev.example',
442
+ },
443
+ status: {
444
+ conditions: [{ lastTransitionTime: '2025-07-24T01:27:29Z', message: 'ready', status: 'True', type: 'Ready' }],
445
+ hostIP: '10.0.0.1',
446
+ podIP: '10.0.0.2',
447
+ qosClass: 'BestEffort',
448
+ },
449
+ },
450
+ events: [],
451
+ } as any);