@spinnaker/cloudrun 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 (97) hide show
  1. package/dist/cloudrun.module.d.ts +1 -1
  2. package/dist/common/ComponentUrlDetails.d.ts +7 -0
  3. package/dist/common/LoadBalancerMessage.d.ts +4 -0
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/instance/details/CloudrunInstanceDetails.d.ts +23 -0
  8. package/dist/loadBalancer/configure/wizard/CloudrunLoadBalancerModal.d.ts +32 -0
  9. package/dist/loadBalancer/details/CloudrunLoadBalancerActions.d.ts +3 -0
  10. package/dist/loadBalancer/details/sections.d.ts +7 -0
  11. package/dist/loadBalancer/details/useCloudrunLoadBalancerDetails.d.ts +4 -0
  12. package/dist/loadBalancer/index.d.ts +4 -2
  13. package/dist/loadBalancer/loadBalancerTransformer.d.ts +2 -6
  14. package/dist/manifest/manifest.service.d.ts +5 -0
  15. package/dist/manifest/manifestCommandBuilder.service.d.ts +1 -1
  16. package/dist/pipeline/stages/deployManifest/manifestStatus/DeployStatus.d.ts +1 -0
  17. package/dist/pipeline/stages/deployManifest/manifestStatus/ManifestDetailsLink.d.ts +6 -3
  18. package/dist/pipeline/stages/editLoadBalancer/CloudrunEditLoadBalancerExecutionDetails.d.ts +7 -0
  19. package/dist/pipeline/stages/editLoadBalancer/CloudrunEditLoadBalancerStageConfig.d.ts +30 -0
  20. package/dist/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.d.ts +2 -1
  21. package/dist/serverGroup/configure/serverGroupCommandBuilder.service.d.ts +4 -6
  22. package/dist/serverGroup/configure/wizard/BasicSettings.d.ts +5 -2
  23. package/dist/serverGroup/configure/wizard/serverGroupWizard.d.ts +18 -6
  24. package/dist/serverGroup/details/CloudrunServerGroupActions.d.ts +6 -0
  25. package/dist/serverGroup/details/cloudrunServerGroupDetailsGetter.d.ts +3 -0
  26. package/dist/serverGroup/details/sections.d.ts +5 -0
  27. package/dist/serverGroup/index.d.ts +3 -1
  28. package/dist/serverGroup/serverGroupTransformer.service.d.ts +1 -5
  29. package/package.json +4 -11
  30. package/src/cloudrun.module.spec.ts +22 -0
  31. package/src/cloudrun.module.ts +54 -59
  32. package/src/common/ComponentUrlDetails.tsx +25 -0
  33. package/src/common/LoadBalancerMessage.tsx +17 -0
  34. package/src/index.ts +2 -1
  35. package/src/instance/details/CloudrunInstanceDetails.tsx +164 -0
  36. package/src/loadBalancer/configure/wizard/CloudrunLoadBalancerModal.spec.tsx +85 -0
  37. package/src/loadBalancer/configure/wizard/CloudrunLoadBalancerModal.tsx +395 -0
  38. package/src/loadBalancer/details/CloudrunLoadBalancerActions.tsx +66 -0
  39. package/src/loadBalancer/details/sections.spec.ts +19 -0
  40. package/src/loadBalancer/details/sections.tsx +91 -0
  41. package/src/loadBalancer/details/useCloudrunLoadBalancerDetails.ts +56 -0
  42. package/src/loadBalancer/index.ts +4 -2
  43. package/src/loadBalancer/loadBalancerTransformer.ts +3 -13
  44. package/src/manifest/manifest.service.ts +18 -1
  45. package/src/manifest/manifestCommandBuilder.service.ts +46 -53
  46. package/src/pipeline/stages/deployManifest/manifestStatus/DeployStatus.tsx +33 -4
  47. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestDetailsLink.spec.tsx +35 -0
  48. package/src/pipeline/stages/deployManifest/manifestStatus/ManifestDetailsLink.tsx +10 -5
  49. package/src/pipeline/stages/editLoadBalancer/CloudrunEditLoadBalancerExecutionDetails.tsx +70 -0
  50. package/src/pipeline/stages/editLoadBalancer/CloudrunEditLoadBalancerStageConfig.tsx +245 -0
  51. package/src/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.spec.ts +205 -0
  52. package/src/pipeline/stages/editLoadBalancer/cloudrunEditLoadBalancerStage.ts +14 -71
  53. package/src/serverGroup/configure/serverGroupCommandBuilder.service.ts +5 -15
  54. package/src/serverGroup/configure/wizard/BasicSettings.tsx +10 -8
  55. package/src/serverGroup/configure/wizard/serverGroupWizard.tsx +59 -15
  56. package/src/serverGroup/details/CloudrunServerGroupActions.tsx +80 -0
  57. package/src/serverGroup/details/cloudrunServerGroupDetailsGetter.spec.ts +46 -0
  58. package/src/serverGroup/details/cloudrunServerGroupDetailsGetter.ts +53 -0
  59. package/src/serverGroup/details/sections.tsx +60 -0
  60. package/src/serverGroup/index.ts +3 -1
  61. package/src/serverGroup/routerConsumers.spec.tsx +152 -0
  62. package/src/serverGroup/serverGroupTransformer.service.ts +2 -12
  63. package/dist/common/componentUrlDetails.component.d.ts +0 -1
  64. package/dist/common/conditionalDescriptionListItem.component.d.ts +0 -1
  65. package/dist/common/loadBalancerMessage.component.d.ts +0 -1
  66. package/dist/instance/details/details.controller.d.ts +0 -1
  67. package/dist/loadBalancer/configure/wizard/allocationConfigurationRow.component.d.ts +0 -1
  68. package/dist/loadBalancer/configure/wizard/basicSettings.component.d.ts +0 -1
  69. package/dist/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.d.ts +0 -1
  70. package/dist/loadBalancer/configure/wizard/wizard.controller.d.ts +0 -2
  71. package/dist/loadBalancer/details/details.controller.d.ts +0 -1
  72. package/dist/pipeline/pipeline.module.d.ts +0 -1
  73. package/dist/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.d.ts +0 -1
  74. package/dist/serverGroup/details/details.controller.d.ts +0 -1
  75. package/src/common/componentUrlDetails.component.ts +0 -22
  76. package/src/common/conditionalDescriptionListItem.component.ts +0 -37
  77. package/src/common/loadBalancerMessage.component.html +0 -11
  78. package/src/common/loadBalancerMessage.component.ts +0 -13
  79. package/src/instance/details/details.controller.ts +0 -84
  80. package/src/instance/details/details.html +0 -68
  81. package/src/loadBalancer/configure/wizard/allocationConfigurationRow.component.ts +0 -70
  82. package/src/loadBalancer/configure/wizard/basicSettings.component.html +0 -44
  83. package/src/loadBalancer/configure/wizard/basicSettings.component.ts +0 -86
  84. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.html +0 -45
  85. package/src/loadBalancer/configure/wizard/stageAllocationConfigurationRow.component.ts +0 -84
  86. package/src/loadBalancer/configure/wizard/wizard.controller.ts +0 -157
  87. package/src/loadBalancer/configure/wizard/wizard.html +0 -39
  88. package/src/loadBalancer/configure/wizard/wizard.less +0 -9
  89. package/src/loadBalancer/details/details.controller.ts +0 -140
  90. package/src/loadBalancer/details/details.html +0 -89
  91. package/src/pipeline/pipeline.module.ts +0 -6
  92. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerExecutionDetails.html +0 -33
  93. package/src/pipeline/stages/editLoadBalancer/editLoadBalancerStage.html +0 -51
  94. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.controller.ts +0 -65
  95. package/src/pipeline/stages/editLoadBalancer/loadBalancerChoice.modal.html +0 -53
  96. package/src/serverGroup/details/details.controller.ts +0 -134
  97. package/src/serverGroup/details/details.html +0 -94
@@ -0,0 +1,245 @@
1
+ import { cloneDeep } from 'lodash';
2
+ import React from 'react';
3
+
4
+ import type { ILoadBalancer, IStageConfigProps } from '@spinnaker/core';
5
+ import { AccountTag, ReactModal } from '@spinnaker/core';
6
+
7
+ import { getLoadBalancerCompositeKey } from './CloudrunEditLoadBalancerExecutionDetails';
8
+ import { LoadBalancerMessage } from '../../../common/LoadBalancerMessage';
9
+ import { CloudrunLoadBalancerModal } from '../../../loadBalancer/configure/wizard/CloudrunLoadBalancerModal';
10
+
11
+ export class CloudrunEditLoadBalancerStageConfig extends React.Component<IStageConfigProps> {
12
+ public componentDidMount(): void {
13
+ this.props.updateStage({
14
+ loadBalancers: this.props.stage.loadBalancers || [],
15
+ cloudProvider: 'cloudrun',
16
+ });
17
+ }
18
+
19
+ private addLoadBalancer = (): void => {
20
+ CloudrunLoadBalancerChoiceModal.show({ application: this.props.application })
21
+ .then((newLoadBalancer) => {
22
+ this.props.updateStage({ loadBalancers: [...(this.props.stage.loadBalancers || []), newLoadBalancer] });
23
+ })
24
+ .catch(() => undefined);
25
+ };
26
+
27
+ private editLoadBalancer = (index: number): void => {
28
+ CloudrunLoadBalancerModal.show({
29
+ app: this.props.application,
30
+ loadBalancer: cloneDeep(this.props.stage.loadBalancers[index]),
31
+ isNew: false,
32
+ forPipelineConfig: true,
33
+ })
34
+ .then((updatedLoadBalancer) => {
35
+ const loadBalancers = [...this.props.stage.loadBalancers];
36
+ loadBalancers[index] = updatedLoadBalancer;
37
+ this.props.updateStage({ loadBalancers });
38
+ })
39
+ .catch(() => undefined);
40
+ };
41
+
42
+ private removeLoadBalancer = (index: number): void => {
43
+ const loadBalancers = [...this.props.stage.loadBalancers];
44
+ loadBalancers.splice(index, 1);
45
+ this.props.updateStage({ loadBalancers });
46
+ };
47
+
48
+ public render() {
49
+ const { pipeline, stage } = this.props;
50
+ if (pipeline.strategy) {
51
+ return null;
52
+ }
53
+ return (
54
+ <div className="well well-sm clearfix">
55
+ <div className="row">
56
+ <div className="col-md-12">
57
+ <h4 className="text-left">Load Balancers</h4>
58
+ </div>
59
+ </div>
60
+ <div className="row">
61
+ <div className="col-md-12">
62
+ <table className="table table-condensed">
63
+ <thead>
64
+ <tr>
65
+ <th>Account</th>
66
+ <th>Name</th>
67
+ <th>Region</th>
68
+ <th>Actions</th>
69
+ </tr>
70
+ </thead>
71
+ <tbody>
72
+ {(stage.loadBalancers || []).map((loadBalancer: ILoadBalancer, index: number) => (
73
+ <tr
74
+ key={`${loadBalancer.credentials || loadBalancer.account}:${loadBalancer.name}:${
75
+ loadBalancer.region
76
+ }`}
77
+ >
78
+ <td>
79
+ <AccountTag account={loadBalancer.credentials || loadBalancer.account} />
80
+ </td>
81
+ <td>{loadBalancer.name}</td>
82
+ <td>{loadBalancer.region}</td>
83
+ <td className="condensed-actions">
84
+ <button
85
+ type="button"
86
+ className="btn btn-sm btn-link"
87
+ onClick={() => this.editLoadBalancer(index)}
88
+ >
89
+ <span className="glyphicon glyphicon-edit" title="Edit" />
90
+ </button>
91
+ <button
92
+ type="button"
93
+ className="btn btn-sm btn-link pad-left"
94
+ onClick={() => this.removeLoadBalancer(index)}
95
+ >
96
+ <span className="glyphicon glyphicon-trash" title="Remove" />
97
+ </button>
98
+ </td>
99
+ </tr>
100
+ ))}
101
+ </tbody>
102
+ <tfoot>
103
+ <tr>
104
+ <td colSpan={8}>
105
+ <button type="button" className="btn btn-block btn-sm add-new" onClick={this.addLoadBalancer}>
106
+ <span className="glyphicon glyphicon-plus-sign" /> Add load balancer
107
+ </button>
108
+ </td>
109
+ </tr>
110
+ </tfoot>
111
+ </table>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ );
116
+ }
117
+ }
118
+
119
+ interface ICloudrunLoadBalancerChoiceModalProps {
120
+ application: IStageConfigProps['application'];
121
+ closeModal?: (loadBalancer: ILoadBalancer) => void;
122
+ dismissModal?: () => void;
123
+ }
124
+
125
+ interface ICloudrunLoadBalancerChoiceModalState {
126
+ loading: boolean;
127
+ loadBalancers: ILoadBalancer[];
128
+ selectedLoadBalancer?: ILoadBalancer;
129
+ }
130
+
131
+ export class CloudrunLoadBalancerChoiceModal extends React.Component<
132
+ ICloudrunLoadBalancerChoiceModalProps,
133
+ ICloudrunLoadBalancerChoiceModalState
134
+ > {
135
+ public static show(props: ICloudrunLoadBalancerChoiceModalProps): Promise<ILoadBalancer> {
136
+ return ReactModal.show(CloudrunLoadBalancerChoiceModal, props);
137
+ }
138
+
139
+ private mounted = false;
140
+
141
+ public state: ICloudrunLoadBalancerChoiceModalState = { loading: true, loadBalancers: [] };
142
+
143
+ public componentDidMount(): void {
144
+ this.mounted = true;
145
+ this.props.application.loadBalancers
146
+ .ready()
147
+ .then(() => {
148
+ if (!this.mounted) {
149
+ return;
150
+ }
151
+ const loadBalancers = (this.props.application.loadBalancers.data as ILoadBalancer[]).filter(
152
+ (candidate) => candidate.cloudProvider === 'cloudrun',
153
+ );
154
+ this.setState({ loadBalancers, selectedLoadBalancer: loadBalancers[0], loading: false });
155
+ })
156
+ .catch(() => {
157
+ if (this.mounted) {
158
+ this.setState({ loading: false });
159
+ }
160
+ });
161
+ }
162
+
163
+ public componentWillUnmount(): void {
164
+ this.mounted = false;
165
+ }
166
+
167
+ private submit = (): void => {
168
+ CloudrunLoadBalancerModal.show({
169
+ app: this.props.application,
170
+ loadBalancer: cloneDeep(this.state.selectedLoadBalancer),
171
+ isNew: false,
172
+ forPipelineConfig: true,
173
+ })
174
+ .then(this.props.closeModal)
175
+ .catch(() => undefined);
176
+ };
177
+
178
+ public render() {
179
+ const { dismissModal } = this.props;
180
+ const { loading, loadBalancers, selectedLoadBalancer } = this.state;
181
+ return (
182
+ <div>
183
+ <div className="modal-header">
184
+ <button type="button" className="close" onClick={dismissModal}>
185
+ <span>&times;</span>
186
+ </button>
187
+ <h4 className="modal-title">Select Load Balancer</h4>
188
+ </div>
189
+ <div className="modal-body">
190
+ {loading && (
191
+ <div style={{ height: 200 }} className="horizontal center middle">
192
+ Loading...
193
+ </div>
194
+ )}
195
+ {!loading && loadBalancers.length === 0 && <LoadBalancerMessage showCreateMessage={true} />}
196
+ {!loading && loadBalancers.length > 0 && (
197
+ <div className="form-horizontal">
198
+ <div className="form-group">
199
+ <div className="col-md-3 sm-label-right">
200
+ <b>Load Balancer</b>
201
+ </div>
202
+ <div className="col-md-7">
203
+ <select
204
+ className="form-control input-sm"
205
+ value={getLoadBalancerCompositeKey(selectedLoadBalancer)}
206
+ onChange={(event) =>
207
+ this.setState({
208
+ selectedLoadBalancer: loadBalancers.find(
209
+ (loadBalancer) => getLoadBalancerCompositeKey(loadBalancer) === event.target.value,
210
+ ),
211
+ })
212
+ }
213
+ >
214
+ {loadBalancers.map((loadBalancer) => (
215
+ <option
216
+ key={getLoadBalancerCompositeKey(loadBalancer)}
217
+ value={getLoadBalancerCompositeKey(loadBalancer)}
218
+ >
219
+ {getLoadBalancerOptionLabel(loadBalancer)}
220
+ </option>
221
+ ))}
222
+ </select>
223
+ </div>
224
+ </div>
225
+ </div>
226
+ )}
227
+ </div>
228
+ <div className="modal-footer">
229
+ <button type="button" className="btn btn-default" onClick={dismissModal}>
230
+ Cancel
231
+ </button>
232
+ {loadBalancers.length > 0 && (
233
+ <button type="button" className="btn btn-primary" onClick={this.submit}>
234
+ <span className="far fa-check-circle" /> Edit
235
+ </button>
236
+ )}
237
+ </div>
238
+ </div>
239
+ );
240
+ }
241
+ }
242
+
243
+ export function getLoadBalancerOptionLabel(loadBalancer: ILoadBalancer): string {
244
+ return [loadBalancer.account, loadBalancer.name, loadBalancer.region].filter(Boolean).join(' ');
245
+ }
@@ -0,0 +1,205 @@
1
+ import { mount } from 'enzyme';
2
+ import React from 'react';
3
+ import { BehaviorSubject } from 'rxjs';
4
+
5
+ import { ExecutionDetailsTasks } from '@spinnaker/core';
6
+
7
+ import {
8
+ CloudrunEditLoadBalancerExecutionDetails,
9
+ getLoadBalancerCompositeKey,
10
+ } from './CloudrunEditLoadBalancerExecutionDetails';
11
+ import {
12
+ CloudrunEditLoadBalancerStageConfig,
13
+ CloudrunLoadBalancerChoiceModal,
14
+ getLoadBalancerOptionLabel,
15
+ } from './CloudrunEditLoadBalancerStageConfig';
16
+ import { CLOUDRUN_EDIT_LOAD_BALANCER_STAGE_CONFIG } from './cloudrunEditLoadBalancerStage';
17
+ import { CloudrunLoadBalancerModal } from '../../../loadBalancer/configure/wizard/CloudrunLoadBalancerModal';
18
+ import {
19
+ findCloudrunLoadBalancer,
20
+ useCloudrunLoadBalancerDetails,
21
+ } from '../../../loadBalancer/details/useCloudrunLoadBalancerDetails';
22
+
23
+ describe('Cloud Run edit load balancer stage', () => {
24
+ function buildChoiceModal(overrides: any = {}) {
25
+ const props = {
26
+ application: {
27
+ loadBalancers: {
28
+ data: [],
29
+ ready: jasmine.createSpy('ready').and.returnValue(Promise.resolve()),
30
+ },
31
+ },
32
+ closeModal: jasmine.createSpy('closeModal'),
33
+ dismissModal: jasmine.createSpy('dismissModal'),
34
+ ...overrides,
35
+ } as any;
36
+
37
+ return new CloudrunLoadBalancerChoiceModal(props);
38
+ }
39
+
40
+ it('uses a composite key for load balancer selection', () => {
41
+ expect(getLoadBalancerCompositeKey({ name: 'default', account: 'test', region: 'us-central1' } as any)).toBe(
42
+ 'default:test:us-central1',
43
+ );
44
+ expect(getLoadBalancerCompositeKey({ name: 'default', account: 'prod', region: 'us-central1' } as any)).toBe(
45
+ 'default:prod:us-central1',
46
+ );
47
+ });
48
+
49
+ it('matches load balancer details by region', () => {
50
+ const loadBalancers = [
51
+ { name: 'default', account: 'test', region: 'europe-west1' },
52
+ { name: 'default', account: 'test', region: 'us-central1' },
53
+ ] as any;
54
+
55
+ expect(
56
+ findCloudrunLoadBalancer(loadBalancers, {
57
+ name: 'default',
58
+ accountId: 'test',
59
+ region: 'us-central1',
60
+ } as any),
61
+ ).toBe(loadBalancers[1]);
62
+ });
63
+
64
+ it('includes region in load balancer option labels', () => {
65
+ expect(getLoadBalancerOptionLabel({ name: 'default', account: 'test', region: 'us-central1' } as any)).toBe(
66
+ 'test default us-central1',
67
+ );
68
+ });
69
+
70
+ it('registers React execution details before task details', () => {
71
+ expect(CLOUDRUN_EDIT_LOAD_BALANCER_STAGE_CONFIG.executionDetailsSections).toEqual([
72
+ CloudrunEditLoadBalancerExecutionDetails,
73
+ ExecutionDetailsTasks,
74
+ ]);
75
+ });
76
+
77
+ it('stops loading when load balancers fail to load', async () => {
78
+ const modal = buildChoiceModal({
79
+ application: {
80
+ loadBalancers: {
81
+ data: [],
82
+ ready: jasmine.createSpy('ready').and.returnValue(Promise.reject(new Error('load failed'))),
83
+ },
84
+ },
85
+ });
86
+ spyOn(modal, 'setState');
87
+
88
+ modal.componentDidMount();
89
+ await Promise.resolve();
90
+ await Promise.resolve();
91
+
92
+ expect(modal.setState).toHaveBeenCalledWith({ loading: false });
93
+ });
94
+
95
+ it('does not update load balancer choices after unmount', async () => {
96
+ let resolveReady: () => void;
97
+ const ready = new Promise<void>((resolve) => {
98
+ resolveReady = resolve;
99
+ });
100
+ const modal = buildChoiceModal({
101
+ application: {
102
+ loadBalancers: {
103
+ data: [{ name: 'default', account: 'test', region: 'us-central1', cloudProvider: 'cloudrun' }],
104
+ ready: jasmine.createSpy('ready').and.returnValue(ready),
105
+ },
106
+ },
107
+ });
108
+ spyOn(modal, 'setState');
109
+
110
+ modal.componentDidMount();
111
+ (modal as any).componentWillUnmount?.();
112
+ resolveReady!();
113
+ await ready;
114
+ await Promise.resolve();
115
+
116
+ expect(modal.setState).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('handles dismissal of the load balancer edit modal', () => {
120
+ const catchSpy = jasmine.createSpy('catch');
121
+ const modalResult = { then: jasmine.createSpy('then').and.returnValue({ catch: catchSpy }) };
122
+ spyOn(CloudrunLoadBalancerModal, 'show').and.returnValue(modalResult as any);
123
+ const modal = buildChoiceModal();
124
+ modal.state = { ...modal.state, selectedLoadBalancer: { name: 'default' } as any };
125
+
126
+ (modal as any).submit();
127
+
128
+ expect(catchSpy).toHaveBeenCalled();
129
+ });
130
+
131
+ it('handles dismissal of the load balancer choice modal when adding to a stage', () => {
132
+ const catchSpy = jasmine.createSpy('catch');
133
+ const modalResult = { then: jasmine.createSpy('then').and.returnValue({ catch: catchSpy }) };
134
+ spyOn(CloudrunLoadBalancerChoiceModal, 'show').and.returnValue(modalResult as any);
135
+ const stageConfig = new CloudrunEditLoadBalancerStageConfig({
136
+ application: {},
137
+ pipeline: {},
138
+ stage: { loadBalancers: [] },
139
+ updateStage: jasmine.createSpy('updateStage'),
140
+ } as any);
141
+
142
+ (stageConfig as any).addLoadBalancer();
143
+
144
+ expect(catchSpy).toHaveBeenCalled();
145
+ });
146
+
147
+ it('handles dismissal of the load balancer edit modal when editing a stage load balancer', () => {
148
+ const catchSpy = jasmine.createSpy('catch');
149
+ const modalResult = { then: jasmine.createSpy('then').and.returnValue({ catch: catchSpy }) };
150
+ spyOn(CloudrunLoadBalancerModal, 'show').and.returnValue(modalResult as any);
151
+ const stageConfig = new CloudrunEditLoadBalancerStageConfig({
152
+ application: {},
153
+ pipeline: {},
154
+ stage: { loadBalancers: [{ name: 'default' }] },
155
+ updateStage: jasmine.createSpy('updateStage'),
156
+ } as any);
157
+
158
+ (stageConfig as any).editLoadBalancer(0);
159
+
160
+ expect(catchSpy).toHaveBeenCalled();
161
+ });
162
+
163
+ it('updates details when application load balancer data refreshes', async () => {
164
+ const firstLoadBalancer = { name: 'default', account: 'test', region: 'us-central1', image: 'old' };
165
+ const refreshedLoadBalancer = { name: 'default', account: 'test', region: 'us-central1', image: 'new' };
166
+ const status$ = new BehaviorSubject({
167
+ status: 'FETCHED',
168
+ loaded: true,
169
+ lastRefresh: 1,
170
+ data: [firstLoadBalancer],
171
+ });
172
+ const app = {
173
+ getDataSource: jasmine.createSpy('getDataSource').and.returnValue({
174
+ status$,
175
+ refresh: jasmine.createSpy('refresh'),
176
+ }),
177
+ loadBalancers: {
178
+ data: [firstLoadBalancer],
179
+ ready: jasmine.createSpy('ready').and.returnValue(Promise.resolve()),
180
+ },
181
+ } as any;
182
+ const onChange = jasmine.createSpy('onChange');
183
+ const loadBalancerParams = { name: 'default', accountId: 'test', region: 'us-central1' } as any;
184
+
185
+ function TestComponent() {
186
+ const details = useCloudrunLoadBalancerDetails({
187
+ app,
188
+ loadBalancerParams,
189
+ autoClose: jasmine.createSpy(),
190
+ } as any);
191
+ React.useEffect(() => onChange(details), [details.data, details.loading, details.error]);
192
+ return null;
193
+ }
194
+
195
+ const wrapper = mount(React.createElement(TestComponent));
196
+ await Promise.resolve();
197
+ wrapper.setProps({});
198
+
199
+ app.loadBalancers.data = [refreshedLoadBalancer];
200
+ status$.next({ status: 'FETCHED', loaded: true, lastRefresh: 2, data: [refreshedLoadBalancer] });
201
+ wrapper.setProps({});
202
+
203
+ expect(onChange.calls.mostRecent().args[0].data).toBe(refreshedLoadBalancer);
204
+ });
205
+ });
@@ -1,74 +1,17 @@
1
- import type { IController } from 'angular';
2
- import { module } from 'angular';
3
- import type { IModalService } from 'angular-ui-bootstrap';
4
- import { cloneDeep } from 'lodash';
1
+ import type { IStageTypeConfig } from '@spinnaker/core';
2
+ import { ExecutionDetailsTasks, Registry } from '@spinnaker/core';
5
3
 
6
- import type { ILoadBalancer } from '@spinnaker/core';
7
- import { CloudProviderRegistry, Registry } from '@spinnaker/core';
4
+ import { CloudrunEditLoadBalancerExecutionDetails } from './CloudrunEditLoadBalancerExecutionDetails';
5
+ import { CloudrunEditLoadBalancerStageConfig } from './CloudrunEditLoadBalancerStageConfig';
8
6
 
9
- import { CLOUDRUN_LOAD_BALANCER_CHOICE_MODAL_CTRL } from './loadBalancerChoice.modal.controller';
7
+ export const CLOUDRUN_EDIT_LOAD_BALANCER_STAGE_CONFIG: IStageTypeConfig = {
8
+ label: 'Edit Load Balancer (Cloud Run)',
9
+ description: 'Edits a load balancer',
10
+ key: 'upsertCloudrunLoadBalancers',
11
+ cloudProvider: 'cloudrun',
12
+ component: CloudrunEditLoadBalancerStageConfig,
13
+ executionDetailsSections: [CloudrunEditLoadBalancerExecutionDetails, ExecutionDetailsTasks],
14
+ validators: [],
15
+ };
10
16
 
11
- class CloudrunEditLoadBalancerStageCtrl implements IController {
12
- public static $inject = ['$scope', '$uibModal'];
13
- constructor(public $scope: any, private $uibModal: IModalService) {
14
- $scope.stage.loadBalancers = $scope.stage.loadBalancers || [];
15
- $scope.stage.cloudProvider = 'cloudrun';
16
- }
17
-
18
- public addLoadBalancer(): void {
19
- this.$uibModal
20
- .open({
21
- templateUrl: require('./loadBalancerChoice.modal.html'),
22
- controller: `cloudrunLoadBalancerChoiceModelCtrl as ctrl`,
23
- resolve: {
24
- application: () => this.$scope.application,
25
- },
26
- })
27
- .result.then((newLoadBalancer: ILoadBalancer) => {
28
- this.$scope.stage.loadBalancers.push(newLoadBalancer);
29
- })
30
- .catch(() => {});
31
- }
32
-
33
- public editLoadBalancer(index: number) {
34
- const config = CloudProviderRegistry.getValue('cloudrun', 'loadBalancer');
35
- this.$uibModal
36
- .open({
37
- templateUrl: config.createLoadBalancerTemplateUrl,
38
- controller: `${config.createLoadBalancerController} as ctrl`,
39
- size: 'lg',
40
- resolve: {
41
- application: () => this.$scope.application,
42
- loadBalancer: () => cloneDeep(this.$scope.stage.loadBalancers[index]),
43
- isNew: () => false,
44
- forPipelineConfig: () => true,
45
- },
46
- })
47
- .result.then((updatedLoadBalancer: ILoadBalancer) => {
48
- this.$scope.stage.loadBalancers[index] = updatedLoadBalancer;
49
- })
50
- .catch(() => {});
51
- }
52
-
53
- public removeLoadBalancer(index: number): void {
54
- this.$scope.stage.loadBalancers.splice(index, 1);
55
- }
56
- }
57
-
58
- export const CLOUDRUN_EDIT_LOAD_BALANCER_STAGE = 'spinnaker.cloudrun.pipeline.stage.editLoadBalancerStage';
59
- module(CLOUDRUN_EDIT_LOAD_BALANCER_STAGE, [CLOUDRUN_LOAD_BALANCER_CHOICE_MODAL_CTRL])
60
- .config(() => {
61
- Registry.pipeline.registerStage({
62
- label: 'Edit Load Balancer (Cloud Run)',
63
- description: 'Edits a load balancer',
64
- key: 'upsertCloudrunLoadBalancers',
65
- cloudProvider: 'cloudrun',
66
- templateUrl: require('./editLoadBalancerStage.html'),
67
- executionDetailsUrl: require('./editLoadBalancerExecutionDetails.html'),
68
- executionConfigSections: ['editLoadBalancerConfig', 'taskStatus'],
69
- controller: 'cloudrunEditLoadBalancerStageCtrl',
70
- controllerAs: 'editLoadBalancerStageCtrl',
71
- validators: [],
72
- });
73
- })
74
- .controller('cloudrunEditLoadBalancerStageCtrl', CloudrunEditLoadBalancerStageCtrl);
17
+ Registry.pipeline.registerStage(CLOUDRUN_EDIT_LOAD_BALANCER_STAGE_CONFIG);
@@ -1,6 +1,4 @@
1
- import { module } from 'angular';
2
1
  import { cloneDeep } from 'lodash';
3
- import { $q } from 'ngimport';
4
2
 
5
3
  import type {
6
4
  Application,
@@ -91,7 +89,7 @@ const getSubmitButtonLabel = (mode: string): string => {
91
89
 
92
90
  export class CloudrunV2ServerGroupCommandBuilder {
93
91
  // new add servergroup
94
- public buildNewServerGroupCommand(app: Application): PromiseLike<ICloudrunServerGroupCommandData> {
92
+ public buildNewServerGroupCommand(app: Application): Promise<ICloudrunServerGroupCommandData> {
95
93
  return CloudrunServerGroupCommandBuilder.buildNewServerGroupCommand(app, 'cloudrun', 'create');
96
94
  }
97
95
 
@@ -113,7 +111,6 @@ export class CloudrunV2ServerGroupCommandBuilder {
113
111
  }
114
112
 
115
113
  export class CloudrunServerGroupCommandBuilder {
116
- public static $inject = ['$q'];
117
114
  public static ServerGroupCommandIsValid(command: ICloudrunServerGroupCommand): boolean {
118
115
  if (!command.moniker) {
119
116
  return false;
@@ -156,7 +153,7 @@ export class CloudrunServerGroupCommandBuilder {
156
153
  cluster: CloudrunDeployDescription,
157
154
  _stage: IStage,
158
155
  pipeline: IPipeline,
159
- ): PromiseLike<ICloudrunServerGroupCommandData> {
156
+ ): Promise<ICloudrunServerGroupCommandData> {
160
157
  return CloudrunServerGroupCommandBuilder.buildNewServerGroupCommand(app, 'cloudrun', 'editPipeline').then(
161
158
  (command: ICloudrunServerGroupCommandData) => {
162
159
  command = {
@@ -197,14 +194,14 @@ export class CloudrunServerGroupCommandBuilder {
197
194
  app: Application,
198
195
  sourceAccount: string,
199
196
  mode: string,
200
- ): PromiseLike<ICloudrunServerGroupCommandData> {
197
+ ): Promise<ICloudrunServerGroupCommandData> {
201
198
  const dataToFetch = {
202
199
  accounts: AccountService.getAllAccountDetailsForProvider('cloudrun'),
203
200
  artifactAccounts: AccountService.getArtifactAccounts(),
204
201
  };
205
202
 
206
- return $q.all(dataToFetch).then((backingData: { accounts: IAccountDetails[] }) => {
207
- const { accounts } = backingData;
203
+ return Promise.all([dataToFetch.accounts, dataToFetch.artifactAccounts]).then(([accounts, artifactAccounts]) => {
204
+ const backingData = { accounts, artifactAccounts };
208
205
 
209
206
  const account = accounts.some((a) => a.name === sourceAccount)
210
207
  ? accounts.find((a) => a.name === sourceAccount).name
@@ -245,10 +242,3 @@ export class CloudrunServerGroupCommandBuilder {
245
242
  });
246
243
  }
247
244
  }
248
-
249
- export const CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER = 'spinnaker.cloudrun.serverGroup.commandBuilder.service';
250
-
251
- module(CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER, []).service(
252
- 'cloudrunV2ServerGroupCommandBuilder',
253
- CloudrunV2ServerGroupCommandBuilder,
254
- );
@@ -1,8 +1,8 @@
1
1
  import type { FormikProps } from 'formik';
2
2
  import React from 'react';
3
3
 
4
- import type { Application, IAccount, IServerGroup } from '@spinnaker/core';
5
- import { AccountSelectInput, HelpField, NameUtils, ReactInjector, ServerGroupNamePreview } from '@spinnaker/core';
4
+ import type { Application, IAccount, IRouterInjectedProps, IServerGroup } from '@spinnaker/core';
5
+ import { AccountSelectInput, HelpField, NameUtils, ServerGroupNamePreview, withRouter } from '@spinnaker/core';
6
6
 
7
7
  import type { ICloudrunServerGroupCommandData } from '../serverGroupCommandBuilder.service';
8
8
 
@@ -22,7 +22,7 @@ export interface IServerGroupBasicSettingsState {
22
22
  latestServerGroup: IServerGroup;
23
23
  }
24
24
 
25
- export function ServerGroupBasicSettings({
25
+ export function ServerGroupBasicSettingsComponent({
26
26
  accounts,
27
27
  onAccountSelect,
28
28
  selectedAccount,
@@ -30,7 +30,8 @@ export function ServerGroupBasicSettings({
30
30
  onEnterStack,
31
31
  detailsChanged,
32
32
  app,
33
- }: IServerGroupBasicSettingsProps) {
33
+ stateService,
34
+ }: IServerGroupBasicSettingsProps & IRouterInjectedProps) {
34
35
  const { values } = formik;
35
36
  const { stack = '', freeFormDetails } = values;
36
37
 
@@ -56,11 +57,10 @@ export function ServerGroupBasicSettings({
56
57
  serverGroup: latestServerGroup.name,
57
58
  };
58
59
 
59
- const { $state } = ReactInjector;
60
- if ($state.is('home.applications.application.insight.clusters')) {
61
- $state.go('.serverGroup', params);
60
+ if (stateService.is('home.applications.application.insight.clusters')) {
61
+ stateService.go('.serverGroup', params);
62
62
  } else {
63
- $state.go('^.serverGroup', params);
63
+ stateService.go('^.serverGroup', params);
64
64
  }
65
65
  };
66
66
 
@@ -119,6 +119,8 @@ export function ServerGroupBasicSettings({
119
119
  );
120
120
  }
121
121
 
122
+ export const ServerGroupBasicSettings = withRouter(ServerGroupBasicSettingsComponent);
123
+
122
124
  export interface IWizardServerGroupBasicSettingsProps {
123
125
  formik: FormikProps<ICloudrunServerGroupCommandData>;
124
126
  app: Application;