@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,66 @@
1
+ import React from 'react';
2
+ import { Dropdown, MenuItem } from 'react-bootstrap';
3
+
4
+ import type { ILoadBalancerActionsProps, ILoadBalancerDeleteCommand } from '@spinnaker/core';
5
+ import { ConfirmationModalService, LoadBalancerWriter } from '@spinnaker/core';
6
+
7
+ import type { ICloudrunLoadBalancer } from '../../common/domain';
8
+ import { CloudrunLoadBalancerModal } from '../configure/wizard/CloudrunLoadBalancerModal';
9
+
10
+ export function CloudrunLoadBalancerActions({ app, loadBalancer }: ILoadBalancerActionsProps) {
11
+ const cloudrunLoadBalancer = loadBalancer as ICloudrunLoadBalancer;
12
+ const canDeleteLoadBalancer = cloudrunLoadBalancer.name !== 'default';
13
+
14
+ const editLoadBalancer = () => {
15
+ CloudrunLoadBalancerModal.show({ app, loadBalancer: cloudrunLoadBalancer, isNew: false, forPipelineConfig: false });
16
+ };
17
+ const getConfirmationModalBodyHtml = (): string => {
18
+ const serverGroupNames = cloudrunLoadBalancer.serverGroups.map((serverGroup) => serverGroup.name);
19
+ if (!serverGroupNames?.length) {
20
+ return null;
21
+ }
22
+ if (serverGroupNames.length > 1) {
23
+ const listOfServerGroupNames = serverGroupNames.map((name) => `<li>${name}</li>`).join('');
24
+ return `<div class="alert alert-warning"><p>Deleting <b>${cloudrunLoadBalancer.name}</b> will destroy the following server groups:<ul>${listOfServerGroupNames}</ul></p></div>`;
25
+ }
26
+ return `<div class="alert alert-warning"><p>Deleting <b>${cloudrunLoadBalancer.name}</b> will destroy <b>${serverGroupNames[0]}</b>.</p></div>`;
27
+ };
28
+ const deleteLoadBalancer = () => {
29
+ const submitMethod = () => {
30
+ const command: ILoadBalancerDeleteCommand = {
31
+ cloudProvider: cloudrunLoadBalancer.cloudProvider,
32
+ loadBalancerName: cloudrunLoadBalancer.name,
33
+ credentials: cloudrunLoadBalancer.account,
34
+ };
35
+ return LoadBalancerWriter.deleteLoadBalancer(command, app);
36
+ };
37
+
38
+ ConfirmationModalService.confirm({
39
+ header: `Really delete ${cloudrunLoadBalancer.name}?`,
40
+ buttonText: `Delete ${cloudrunLoadBalancer.name}`,
41
+ body: getConfirmationModalBodyHtml(),
42
+ account: cloudrunLoadBalancer.account,
43
+ taskMonitorConfig: {
44
+ application: app,
45
+ title: `Deleting ${cloudrunLoadBalancer.name}`,
46
+ },
47
+ submitMethod,
48
+ });
49
+ };
50
+
51
+ return (
52
+ <Dropdown className="dropdown" id="cloudrun-load-balancer-actions-dropdown">
53
+ <Dropdown.Toggle className="btn btn-sm btn-primary dropdown-toggle">Load Balancer Actions</Dropdown.Toggle>
54
+ <Dropdown.Menu>
55
+ <MenuItem onClick={editLoadBalancer}>Edit Load Balancer</MenuItem>
56
+ {canDeleteLoadBalancer ? (
57
+ <MenuItem onClick={deleteLoadBalancer}>Delete Load Balancer</MenuItem>
58
+ ) : (
59
+ <MenuItem disabled={true} title="You cannot delete a default service.">
60
+ Delete Load Balancer
61
+ </MenuItem>
62
+ )}
63
+ </Dropdown.Menu>
64
+ </Dropdown>
65
+ );
66
+ }
@@ -0,0 +1,19 @@
1
+ import { compareCloudrunLoadBalancerServerGroups } from './sections';
2
+
3
+ describe('compareCloudrunLoadBalancerServerGroups', () => {
4
+ it('sorts enabled server groups first and names descending within each state', () => {
5
+ const sorted = [
6
+ { name: 'alpha', isDisabled: true },
7
+ { name: 'zulu', isDisabled: false },
8
+ { name: 'alpha', isDisabled: false },
9
+ { name: 'zulu', isDisabled: true },
10
+ ].sort(compareCloudrunLoadBalancerServerGroups as any);
11
+
12
+ expect(sorted.map((serverGroup) => `${serverGroup.isDisabled}:${serverGroup.name}`)).toEqual([
13
+ 'false:zulu',
14
+ 'false:alpha',
15
+ 'true:zulu',
16
+ 'true:alpha',
17
+ ]);
18
+ });
19
+ });
@@ -0,0 +1,91 @@
1
+ import { UISref } from '@uirouter/react';
2
+ import React from 'react';
3
+
4
+ import type { ILoadBalancerDetailsSectionProps } from '@spinnaker/core';
5
+ import { AccountTag, CollapsibleSection, HealthCounts } from '@spinnaker/core';
6
+
7
+ import { ComponentUrlDetails } from '../../common/ComponentUrlDetails';
8
+ import type { ICloudrunLoadBalancer } from '../../common/domain';
9
+
10
+ export function compareCloudrunLoadBalancerServerGroups(a: any, b: any): number {
11
+ if (a.isDisabled !== b.isDisabled) {
12
+ return a.isDisabled ? 1 : -1;
13
+ }
14
+ return b.name.localeCompare(a.name);
15
+ }
16
+
17
+ export function CloudrunLoadBalancerDetailsSection({ loadBalancer }: ILoadBalancerDetailsSectionProps) {
18
+ const cloudrunLoadBalancer = loadBalancer as ICloudrunLoadBalancer;
19
+ return (
20
+ <CollapsibleSection heading="Load Balancer Details" defaultExpanded={true}>
21
+ <dl className="dl-horizontal dl-narrow">
22
+ <dt>In</dt>
23
+ <dd>
24
+ <AccountTag account={cloudrunLoadBalancer.account} />
25
+ </dd>
26
+ <dt>Region</dt>
27
+ <dd>{cloudrunLoadBalancer.region}</dd>
28
+ {!!cloudrunLoadBalancer.serverGroups.length && <dt>Server Groups</dt>}
29
+ {!!cloudrunLoadBalancer.serverGroups.length && (
30
+ <dd>
31
+ <ul>
32
+ {cloudrunLoadBalancer.serverGroups
33
+ .slice()
34
+ .sort(compareCloudrunLoadBalancerServerGroups)
35
+ .map((serverGroup) => (
36
+ <li key={serverGroup.name}>
37
+ <UISref
38
+ to="^.serverGroup"
39
+ params={{
40
+ region: serverGroup.region,
41
+ accountId: serverGroup.account,
42
+ serverGroup: serverGroup.name,
43
+ provider: 'cloudrun',
44
+ }}
45
+ >
46
+ <a>{serverGroup.name}</a>
47
+ </UISref>
48
+ </li>
49
+ ))}
50
+ </ul>
51
+ </dd>
52
+ )}
53
+ </dl>
54
+ </CollapsibleSection>
55
+ );
56
+ }
57
+
58
+ export function CloudrunLoadBalancerStatusSection({ loadBalancer }: ILoadBalancerDetailsSectionProps) {
59
+ return (
60
+ <CollapsibleSection heading="Status" defaultExpanded={true}>
61
+ <HealthCounts className="pull-left" container={loadBalancer.instanceCounts} />
62
+ </CollapsibleSection>
63
+ );
64
+ }
65
+
66
+ export function CloudrunLoadBalancerTrafficSplitSection({ loadBalancer }: ILoadBalancerDetailsSectionProps) {
67
+ const cloudrunLoadBalancer = loadBalancer as ICloudrunLoadBalancer;
68
+ return (
69
+ <CollapsibleSection heading="Traffic Split" defaultExpanded={true}>
70
+ <dl className="dl-horizontal dl-narrow">
71
+ <ul>
72
+ {cloudrunLoadBalancer.split?.trafficTargets?.map((trafficTarget) => (
73
+ <li key={trafficTarget.revisionName}>
74
+ {trafficTarget.revisionName}:<span className="pull-right">{trafficTarget.percent}</span>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ </dl>
79
+ </CollapsibleSection>
80
+ );
81
+ }
82
+
83
+ export function CloudrunLoadBalancerDnsSection({ loadBalancer }: ILoadBalancerDetailsSectionProps) {
84
+ return (
85
+ <CollapsibleSection heading="DNS" defaultExpanded={true}>
86
+ <dl className="dl-narrow">
87
+ <ComponentUrlDetails component={loadBalancer as any} />
88
+ </dl>
89
+ </CollapsibleSection>
90
+ );
91
+ }
@@ -0,0 +1,56 @@
1
+ import { useMemo } from 'react';
2
+
3
+ import { useDataSource } from '@spinnaker/core';
4
+ import type { IUseDetailsHookProps, UseDetailsHook, UseDetailsResult } from '@spinnaker/core';
5
+
6
+ import type { ICloudrunLoadBalancer } from '../../common/domain';
7
+
8
+ export const useCloudrunLoadBalancerDetails: UseDetailsHook<ICloudrunLoadBalancer> = ({
9
+ app,
10
+ loadBalancerParams,
11
+ autoClose,
12
+ }: IUseDetailsHookProps): UseDetailsResult<ICloudrunLoadBalancer> => {
13
+ const dataSource = app.getDataSource('loadBalancers');
14
+ const { data: loadBalancers, loaded, error, refresh } = useDataSource<ICloudrunLoadBalancer[]>(dataSource);
15
+ const data = useMemo(() => {
16
+ if (!loaded || error) {
17
+ return undefined;
18
+ }
19
+
20
+ const loadBalancer = findCloudrunLoadBalancer(loadBalancers, loadBalancerParams);
21
+ if (!loadBalancer) {
22
+ autoClose();
23
+ return undefined;
24
+ }
25
+
26
+ return loadBalancer;
27
+ }, [
28
+ loaded,
29
+ error,
30
+ loadBalancers,
31
+ loadBalancerParams.name,
32
+ loadBalancerParams.accountId,
33
+ loadBalancerParams.region,
34
+ autoClose,
35
+ ]);
36
+
37
+ return {
38
+ data,
39
+ loading: !loaded,
40
+ error: error || null,
41
+ refetch: async () => refresh(),
42
+ };
43
+ };
44
+
45
+ export function findCloudrunLoadBalancer(
46
+ loadBalancers: ICloudrunLoadBalancer[],
47
+ loadBalancerParams: IUseDetailsHookProps['loadBalancerParams'],
48
+ ) {
49
+ return loadBalancers.find((candidate) => {
50
+ return (
51
+ candidate.name === loadBalancerParams.name &&
52
+ candidate.account === loadBalancerParams.accountId &&
53
+ candidate.region === loadBalancerParams.region
54
+ );
55
+ });
56
+ }
@@ -1,3 +1,5 @@
1
1
  export * from './loadBalancerTransformer';
2
- export * from './details/details.controller';
3
- export * from './configure/wizard/wizard.controller';
2
+ export * from './configure/wizard/CloudrunLoadBalancerModal';
3
+ export * from './details/CloudrunLoadBalancerActions';
4
+ export * from './details/sections';
5
+ export * from './details/useCloudrunLoadBalancerDetails';
@@ -1,4 +1,3 @@
1
- import { module } from 'angular';
2
1
  import { camelCase, chain, cloneDeep, filter, get, has, reduce } from 'lodash';
3
2
 
4
3
  import type {
@@ -75,9 +74,7 @@ export class CloudrunLoadBalancerUpsertDescription implements ILoadBalancerUpser
75
74
  }
76
75
 
77
76
  export class CloudrunLoadBalancerTransformer {
78
- public static $inject = ['$q'];
79
- constructor(private $q: ng.IQService) {}
80
- public normalizeLoadBalancer(loadBalancer: ILoadBalancer): PromiseLike<ILoadBalancer> {
77
+ public normalizeLoadBalancer(loadBalancer: ILoadBalancer): Promise<ILoadBalancer> {
81
78
  loadBalancer.provider = loadBalancer.type;
82
79
  loadBalancer.instanceCounts = this.buildInstanceCounts(loadBalancer.serverGroups);
83
80
  loadBalancer.instances = [];
@@ -95,13 +92,13 @@ export class CloudrunLoadBalancerTransformer {
95
92
 
96
93
  const activeServerGroups = filter(loadBalancer.serverGroups, { isDisabled: false });
97
94
  loadBalancer.instances = chain(activeServerGroups).map('instances').flatten().value() as IInstance[];
98
- return this.$q.resolve(loadBalancer);
95
+ return Promise.resolve(loadBalancer);
99
96
  }
100
97
 
101
98
  public convertLoadBalancerForEditing(
102
99
  loadBalancer: ICloudrunLoadBalancer,
103
100
  application: Application,
104
- ): PromiseLike<ICloudrunLoadBalancer> {
101
+ ): Promise<ICloudrunLoadBalancer> {
105
102
  return application
106
103
  .getDataSource('loadBalancers')
107
104
  .ready()
@@ -159,10 +156,3 @@ export class CloudrunLoadBalancerTransformer {
159
156
  return instance as IInstance;
160
157
  }
161
158
  }
162
-
163
- export const CLOUDRUN_LOAD_BALANCER_TRANSFORMER = 'spinnaker.cloudrun.loadBalancer.transformer.service';
164
-
165
- module(CLOUDRUN_LOAD_BALANCER_TRANSFORMER, []).service(
166
- 'cloudrunLoadBalancerTransformer',
167
- CloudrunLoadBalancerTransformer,
168
- );
@@ -10,6 +10,23 @@ export interface IStageManifest {
10
10
  };
11
11
  }
12
12
 
13
+ // A manifest that the artifact-store entity storage feature has replaced with a
14
+ // reference, rather than expanding it back into a real manifest before deck ever
15
+ // sees it (e.g. gate isn't configured to expand these on read).
16
+ export interface IStoredManifestReference {
17
+ type: string;
18
+ reference: string;
19
+ }
20
+
21
+ export function isStoredManifestReference(manifest: unknown): manifest is IStoredManifestReference {
22
+ return (
23
+ !!manifest &&
24
+ typeof manifest === 'object' &&
25
+ (manifest as IStoredManifestReference).type === 'remote/map/base64' &&
26
+ typeof (manifest as IStoredManifestReference).reference === 'string'
27
+ );
28
+ }
29
+
13
30
  export interface IManifestParams {
14
31
  account: string;
15
32
  location: string;
@@ -21,7 +38,7 @@ export type IManifestCallback = (manifest: IManifest) => void;
21
38
  export class CloudrunManifestService {
22
39
  public static subscribe(app: Application, params: IManifestParams, fn: IManifestCallback): () => void {
23
40
  CloudrunManifestService.updateManifest(params, fn);
24
- return app.onRefresh(null, () => CloudrunManifestService.updateManifest(params, fn));
41
+ return app.onRefresh(() => CloudrunManifestService.updateManifest(params, fn));
25
42
  }
26
43
 
27
44
  private static updateManifest(params: IManifestParams, fn: IManifestCallback) {
@@ -1,7 +1,6 @@
1
1
  import { cloneDeep } from 'lodash';
2
- import { $q } from 'ngimport';
3
2
 
4
- import type { Application, IAccountDetails, IArtifactAccount, IMoniker } from '@spinnaker/core';
3
+ import type { Application, IMoniker } from '@spinnaker/core';
5
4
  import { AccountService } from '@spinnaker/core';
6
5
 
7
6
  import { ManifestSource } from './ManifestSource';
@@ -56,61 +55,55 @@ export class CloudrunManifestCommandBuilder {
56
55
  sourceManifest?: any,
57
56
  sourceMoniker?: IMoniker,
58
57
  sourceAccount?: string,
59
- ): PromiseLike<ICloudrunManifestCommandData> {
58
+ ): Promise<ICloudrunManifestCommandData> {
60
59
  const dataToFetch = {
61
60
  accounts: AccountService.getAllAccountDetailsForProvider('cloudrun'),
62
61
  artifactAccounts: AccountService.getArtifactAccounts(),
63
62
  };
64
63
 
65
- return $q
66
- .all(dataToFetch)
67
- .then((backingData: { accounts: IAccountDetails[]; artifactAccounts: IArtifactAccount[] }) => {
68
- const { accounts, artifactAccounts } = backingData;
69
-
70
- const account = accounts.some((a) => a.name === sourceAccount)
71
- ? accounts.find((a) => a.name === sourceAccount).name
72
- : accounts.length
73
- ? accounts[0].name
74
- : null;
75
-
76
- let manifestArtifactAccount: string = null;
77
- const [artifactAccountData] = artifactAccounts;
78
- if (artifactAccountData) {
79
- manifestArtifactAccount = artifactAccountData.name;
80
- }
81
-
82
- const cloudProvider = 'cloudrun';
83
- const moniker = sourceMoniker || {
84
- app: app.name,
85
- };
86
-
87
- const relationships = {
88
- loadBalancers: [] as string[],
89
- securityGroups: [] as string[],
90
- };
91
-
92
- const versioned: any = null;
93
-
94
- return {
95
- command: {
96
- cloudProvider,
97
- manifest: null,
98
- manifests: Array.isArray(sourceManifest)
99
- ? sourceManifest
100
- : sourceManifest != null
101
- ? [sourceManifest]
102
- : null,
103
- relationships,
104
- moniker,
105
- account,
106
- versioned,
107
- manifestArtifactAccount,
108
- source: ManifestSource.TEXT,
109
- },
110
- metadata: {
111
- backingData,
112
- },
113
- } as ICloudrunManifestCommandData;
114
- });
64
+ return Promise.all([dataToFetch.accounts, dataToFetch.artifactAccounts]).then(([accounts, artifactAccounts]) => {
65
+ const backingData = { accounts, artifactAccounts };
66
+
67
+ const account = accounts.some((a) => a.name === sourceAccount)
68
+ ? accounts.find((a) => a.name === sourceAccount).name
69
+ : accounts.length
70
+ ? accounts[0].name
71
+ : null;
72
+
73
+ let manifestArtifactAccount: string = null;
74
+ const [artifactAccountData] = artifactAccounts;
75
+ if (artifactAccountData) {
76
+ manifestArtifactAccount = artifactAccountData.name;
77
+ }
78
+
79
+ const cloudProvider = 'cloudrun';
80
+ const moniker = sourceMoniker || {
81
+ app: app.name,
82
+ };
83
+
84
+ const relationships = {
85
+ loadBalancers: [] as string[],
86
+ securityGroups: [] as string[],
87
+ };
88
+
89
+ const versioned: any = null;
90
+
91
+ return {
92
+ command: {
93
+ cloudProvider,
94
+ manifest: null,
95
+ manifests: Array.isArray(sourceManifest) ? sourceManifest : sourceManifest != null ? [sourceManifest] : null,
96
+ relationships,
97
+ moniker,
98
+ account,
99
+ versioned,
100
+ manifestArtifactAccount,
101
+ source: ManifestSource.TEXT,
102
+ },
103
+ metadata: {
104
+ backingData,
105
+ },
106
+ } as ICloudrunManifestCommandData;
107
+ });
115
108
  }
116
109
  }
@@ -2,11 +2,11 @@ import { get } from 'lodash';
2
2
  import React from 'react';
3
3
 
4
4
  import type { IExecutionDetailsSectionProps, IManifest } from '@spinnaker/core';
5
- import { ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core';
5
+ import { ExecutionDetailsSection, ManifestYaml, StageFailureMessage } from '@spinnaker/core';
6
6
 
7
7
  import { ManifestStatus } from './ManifestStatus';
8
- import type { IStageManifest } from '../../../../manifest/manifest.service';
9
- import { CloudrunManifestService } from '../../../../manifest/manifest.service';
8
+ import type { IStageManifest, IStoredManifestReference } from '../../../../manifest/manifest.service';
9
+ import { CloudrunManifestService, isStoredManifestReference } from '../../../../manifest/manifest.service';
10
10
 
11
11
  export interface IManifestSubscription {
12
12
  id: string;
@@ -36,7 +36,9 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,
36
36
  }
37
37
 
38
38
  public componentDidUpdate(_prevProps: IExecutionDetailsSectionProps, prevState: IDeployStatusState) {
39
- const manifests: IStageManifest[] = get(this.props.stage, ['context', 'outputs.manifests'], []).filter((m) => !!m);
39
+ const manifests: IStageManifest[] = get(this.props.stage, ['context', 'outputs.manifests'], [])
40
+ .filter((m) => !!m)
41
+ .filter((m) => !isStoredManifestReference(m));
40
42
  const manifestIds = manifests.map((m) => CloudrunManifestService.manifestIdentifier(m)).sort();
41
43
  if (prevState.manifestIds.join('') !== manifestIds.join('')) {
42
44
  this.unsubscribeAll();
@@ -69,6 +71,16 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,
69
71
  this.state.subscriptions.forEach(({ unsubscribe }) => unsubscribe());
70
72
  }
71
73
 
74
+ // Manifests the artifact-store entity storage feature replaced with a reference instead
75
+ // of expanding them before deck saw them. We can't determine their kind/name/namespace
76
+ // (and so can't subscribe to live status for them) without fetching the content, so we
77
+ // let the user load them on demand instead, same as a baked manifest's YAML.
78
+ private get storedManifestRefs(): IStoredManifestReference[] {
79
+ return get(this.props.stage, ['context', 'outputs.manifests'], [])
80
+ .filter((m: unknown) => !!m)
81
+ .filter(isStoredManifestReference);
82
+ }
83
+
72
84
  private stageManifestToIManifest(manifest: IStageManifest, account: string): IManifest {
73
85
  return {
74
86
  name: get(manifest, 'metadata.name', ''),
@@ -102,6 +114,23 @@ export class DeployStatus extends React.Component<IExecutionDetailsSectionProps,
102
114
  </div>
103
115
  </div>
104
116
  )}
117
+ {!!this.storedManifestRefs.length && (
118
+ <div className="row">
119
+ <div className="col-md-12">
120
+ <div className="well alert alert-info">
121
+ {this.storedManifestRefs.map((ref, i) => (
122
+ <div key={i}>
123
+ <ManifestYaml
124
+ linkName="Load Manifest Details"
125
+ modalTitle="Manifest"
126
+ manifestUri={ref.reference.replace(/^ref?:\/\//, '')}
127
+ />
128
+ </div>
129
+ ))}
130
+ </div>
131
+ </div>
132
+ </div>
133
+ )}
105
134
  </ExecutionDetailsSection>
106
135
  );
107
136
  }
@@ -0,0 +1,35 @@
1
+ import { shallow } from 'enzyme';
2
+ import React from 'react';
3
+
4
+ import { AccountService } from '@spinnaker/core';
5
+
6
+ import { ManifestDetailsLinkComponent } from './ManifestDetailsLink';
7
+
8
+ describe('Cloud Run ManifestDetailsLink', () => {
9
+ it('builds its link through the injected state service', async () => {
10
+ spyOn(AccountService, 'getAccountDetails').and.returnValue(
11
+ Promise.resolve({ spinnakerKindMap: { Service: 'unclassified' } } as any) as any,
12
+ );
13
+ const href = jasmine.createSpy('href').and.returnValue('#/manifest');
14
+ const component = shallow(
15
+ <ManifestDetailsLinkComponent
16
+ {...({ router: {}, stateParams: {}, stateService: { href } } as any)}
17
+ accountId="test-account"
18
+ linkName="Manifest"
19
+ manifest={{ manifest: { kind: 'Service', metadata: { annotations: {}, name: 'test-service' } } } as any}
20
+ />,
21
+ );
22
+
23
+ await Promise.resolve();
24
+ component.update();
25
+
26
+ expect(href).toHaveBeenCalledWith('home.applications.application.insight.clusters.cloudrunResource', {
27
+ accountId: 'test-account',
28
+ cloudrunResource: 'service test-service',
29
+ provider: 'cloudrun',
30
+ reg: '',
31
+ region: '_',
32
+ });
33
+ expect(component.find('a').prop('href')).toBe('#/manifest');
34
+ });
35
+ });
@@ -1,8 +1,8 @@
1
1
  import { get, trim } from 'lodash';
2
2
  import React from 'react';
3
3
 
4
- import type { IManifest } from '@spinnaker/core';
5
- import { AccountService, ReactInjector } from '@spinnaker/core';
4
+ import type { IManifest, IRouterInjectedProps } from '@spinnaker/core';
5
+ import { AccountService, withRouter } from '@spinnaker/core';
6
6
 
7
7
  const UNMAPPED_K8S_RESOURCE_STATE_KEY = 'cloudrunResource';
8
8
 
@@ -16,14 +16,17 @@ export interface IManifestDetailsState {
16
16
  url: string;
17
17
  }
18
18
 
19
- export class ManifestDetailsLink extends React.Component<IManifestDetailsProps, IManifestDetailsState> {
19
+ export class ManifestDetailsLinkComponent extends React.Component<
20
+ IManifestDetailsProps & IRouterInjectedProps,
21
+ IManifestDetailsState
22
+ > {
20
23
  private spinnakerKindStateMap: { [k: string]: string } = {
21
24
  // keys from clouddriver's CloudrunSpinnakerKindMap
22
25
  serverGroupManagers: 'serverGroupManager',
23
26
  serverGroups: 'serverGroup',
24
27
  };
25
28
 
26
- constructor(props: IManifestDetailsProps) {
29
+ constructor(props: IManifestDetailsProps & IRouterInjectedProps) {
27
30
  super(props);
28
31
  this.state = {
29
32
  url: '',
@@ -73,7 +76,7 @@ export class ManifestDetailsLink extends React.Component<IManifestDetailsProps,
73
76
  const spinnakerKind = this.spinnakerKindFromCloudrunKind(kind, account.spinnakerKindMap);
74
77
  const stateKey = this.spinnakerKindStateMap[spinnakerKind] || UNMAPPED_K8S_RESOURCE_STATE_KEY;
75
78
  const params = this.getStateParams(stateKey);
76
- const url = ReactInjector.$state.href(`home.applications.application.insight.clusters.${stateKey}`, params);
79
+ const url = this.props.stateService.href(`home.applications.application.insight.clusters.${stateKey}`, params);
77
80
  this.setState({ url });
78
81
  });
79
82
  }
@@ -90,3 +93,5 @@ export class ManifestDetailsLink extends React.Component<IManifestDetailsProps,
90
93
  }
91
94
  }
92
95
  }
96
+
97
+ export const ManifestDetailsLink = withRouter(ManifestDetailsLinkComponent);
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+
3
+ import type { IExecutionDetailsSectionProps, ILoadBalancer } from '@spinnaker/core';
4
+ import { AccountTag, ExecutionDetailsSection, StageFailureMessage } from '@spinnaker/core';
5
+
6
+ import type { ICloudrunAllocationDescription } from '../../../loadBalancer/loadBalancerTransformer';
7
+
8
+ export function CloudrunEditLoadBalancerExecutionDetails(props: IExecutionDetailsSectionProps) {
9
+ const { current, name, stage } = props;
10
+ const loadBalancers = ((stage.context || {}).loadBalancers || []) as ILoadBalancer[];
11
+
12
+ return (
13
+ <ExecutionDetailsSection name={name} current={current}>
14
+ <div className="row">
15
+ <div className="col-md-12">
16
+ <table className="table table-condensed">
17
+ <thead>
18
+ <tr>
19
+ <th>Account</th>
20
+ <th>Name</th>
21
+ <th>Region</th>
22
+ <th>Allocations</th>
23
+ </tr>
24
+ </thead>
25
+ <tbody>
26
+ {loadBalancers.map((loadBalancer) => (
27
+ <tr key={getLoadBalancerCompositeKey(loadBalancer)}>
28
+ <td>
29
+ <AccountTag account={loadBalancer.credentials || loadBalancer.account} />
30
+ </td>
31
+ <td>{loadBalancer.name}</td>
32
+ <td>{loadBalancer.region}</td>
33
+ <td>{renderAllocations((loadBalancer as any).splitDescription?.allocationDescriptions)}</td>
34
+ </tr>
35
+ ))}
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
40
+ <StageFailureMessage stage={stage} message={stage.failureMessage} />
41
+ </ExecutionDetailsSection>
42
+ );
43
+ }
44
+
45
+ CloudrunEditLoadBalancerExecutionDetails.title = 'editLoadBalancerConfig';
46
+
47
+ export function getLoadBalancerCompositeKey(loadBalancer: Partial<ILoadBalancer>): string {
48
+ return [
49
+ loadBalancer.name || '',
50
+ loadBalancer.credentials || loadBalancer.account || '',
51
+ loadBalancer.region || '',
52
+ ].join(':');
53
+ }
54
+
55
+ function renderAllocations(allocationDescriptions: ICloudrunAllocationDescription[] = []): React.ReactNode {
56
+ if (!allocationDescriptions.length) {
57
+ return null;
58
+ }
59
+
60
+ return (
61
+ <ul className="list-unstyled no-margin">
62
+ {allocationDescriptions.map((allocation, index) => (
63
+ <li key={`${allocation.revisionName || allocation.target || allocation.cluster || 'allocation'}:${index}`}>
64
+ {allocation.revisionName || allocation.target || allocation.cluster || 'Allocation'}:{' '}
65
+ <span>{allocation.percent}%</span>
66
+ </li>
67
+ ))}
68
+ </ul>
69
+ );
70
+ }