@spinnaker/core 0.11.7 → 0.12.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.
@@ -10,10 +10,11 @@ interface IEnvironmentsRenderProps {
10
10
  style?: React.CSSProperties;
11
11
  children?: React.ReactNode;
12
12
  }
13
- export declare const useOrderedEnvironment: <T extends object>(ref: React.RefObject<HTMLDivElement>, environments: T[]) => {
13
+ export interface OrderedEnvironments<T> {
14
14
  className?: string;
15
15
  style?: React.CSSProperties;
16
16
  environments: T[];
17
- };
17
+ }
18
+ export declare const useOrderedEnvironment: <T extends object>(ref: React.RefObject<HTMLDivElement>, environments: T[]) => OrderedEnvironments<T>;
18
19
  export declare const EnvironmentsRender: React.ForwardRefExoticComponent<IEnvironmentsRenderProps & React.RefAttributes<HTMLDivElement>>;
19
20
  export {};
@@ -106,6 +106,7 @@ export interface MdConfig {
106
106
  updatedAt?: Maybe<Scalars['InstantTime']>;
107
107
  rawConfig?: Maybe<Scalars['String']>;
108
108
  processedConfig?: Maybe<Scalars['String']>;
109
+ previewEnvironmentsConfigured?: Maybe<Scalars['Boolean']>;
109
110
  }
110
111
  export interface MdConstraint {
111
112
  __typename?: 'MdConstraint';
@@ -467,6 +468,9 @@ export declare type FetchApplicationQuery = {
467
468
  application?: Maybe<{
468
469
  __typename?: 'MdApplication';
469
470
  } & Pick<MdApplication, 'id' | 'name' | 'account'> & {
471
+ config?: Maybe<{
472
+ __typename?: 'MdConfig';
473
+ } & Pick<MdConfig, 'id' | 'previewEnvironmentsConfigured'>>;
470
474
  environments: Array<{
471
475
  __typename?: 'MdEnvironment';
472
476
  } & Pick<MdEnvironment, 'isDeleting'> & {
@@ -0,0 +1,8 @@
1
+ import { OrderedEnvironments } from '../environmentBaseElements/EnvironmentsRender';
2
+ import { QueryEnvironment } from './types';
3
+ interface IPreviewEnvironmentsProps {
4
+ orderedEnvironments: OrderedEnvironments<QueryEnvironment>;
5
+ isConfigured?: boolean;
6
+ }
7
+ export declare const PreviewEnvironments: ({ orderedEnvironments, isConfigured }: IPreviewEnvironmentsProps) => JSX.Element;
8
+ export {};
@@ -8,4 +8,4 @@ export declare const MODAL_MAX_WIDTH = 750;
8
8
  export declare const spinnerProps: ISpinnerProps;
9
9
  export declare const ABSOLUTE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss ZZZZ";
10
10
  export declare const MD_CATEGORY = "ManagedDelivery";
11
- export declare const getDocsUrl: (doc: keyof IManagedDeliveryURLs) => string;
11
+ export declare const getDocsUrl: (doc: keyof IManagedDeliveryURLs) => string | undefined;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@spinnaker/core",
3
3
  "license": "Apache-2.0",
4
- "version": "0.11.7",
4
+ "version": "0.12.0",
5
5
  "module": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "scripts": {
@@ -120,5 +120,5 @@
120
120
  "shx": "0.3.3",
121
121
  "typescript": "4.3.5"
122
122
  },
123
- "gitHead": "5b8914288d87ce58b67de4720ebc623b18a3be3e"
123
+ "gitHead": "dc5b117bb381113416c21ae8e4c39ec1c526dd2b"
124
124
  }
@@ -266,4 +266,100 @@ describe('ProviderSelectionService: API', () => {
266
266
  $scope.$digest();
267
267
  expect(provider).toBe('modalProvider');
268
268
  });
269
+
270
+ // Unit tests for the isDisabled function, used to disable and enable buttons that create infrastructure ad-hoc operations
271
+ // in the core module (Create Server Group, Create Load Balancer, Create Firewall, Create Function)
272
+ describe('Toggle Infrastructure Ad-hoc Operations', function () {
273
+ // If an application is configured to only have kubernetes as a cloud provider and only one account exists, which is a kubernetes account,
274
+ // then show the create infrastructure buttons if kubernetesAdHocInfraWritesEnabled is set to true
275
+ it('create infrastructure buttons are enabled for applications with kubernetes cloud provider when kubernetesAdHocInfraWritesEnabled is set to true', () => {
276
+ let isDisabled_result = false;
277
+ hasValue = true;
278
+ const k8s_account = fakeAccount('kubernetes');
279
+ k8s_account.type = 'kubernetes';
280
+
281
+ accounts = [k8s_account];
282
+ let configuration = {
283
+ name: 'kubernetes',
284
+ kubernetesAdHocInfraWritesEnabled: true,
285
+ };
286
+ CloudProviderRegistry.registerProvider('kubernetes', configuration);
287
+ ProviderSelectionService.isDisabled(application).then((isDisable) => {
288
+ isDisabled_result = isDisable;
289
+ });
290
+ $scope.$digest();
291
+ expect(isDisabled_result).toBe(false);
292
+ });
293
+
294
+ // If an application is configured to only have kubernetes as a cloud provider and only one account exists, which is a kubernetes account,
295
+ // then disable the create infrastructure buttons when kubernetesAdHocInfraWritesEnabled is set to false
296
+ it('disable create infrastructure buttons for kubernetes applications when kubernetesAdHocInfraWritesEnabled is false', () => {
297
+ let isDisabled_result = false;
298
+ hasValue = true;
299
+ const k8s_account = fakeAccount('kubernetes');
300
+ k8s_account.type = 'kubernetes';
301
+
302
+ accounts = [k8s_account];
303
+ let configuration = {
304
+ name: 'kubernetes',
305
+ kubernetesAdHocInfraWritesEnabled: false,
306
+ };
307
+ CloudProviderRegistry.registerProvider('kubernetes', configuration);
308
+ ProviderSelectionService.isDisabled(application).then((isDisable) => {
309
+ isDisabled_result = isDisable;
310
+ });
311
+ $scope.$digest();
312
+ expect(isDisabled_result).toBe(true);
313
+ });
314
+
315
+ // If the application is configured to have multiple cloud providers (kuberentes and gce) and different accounts exist with
316
+ // different cloud providers (kuberentes and gce), then create infrastructure buttons appear even though kubernetesAdHocInfraWritesEnabled is false.
317
+ // This is because the buttons allow for ad-hoc operations for the non-kubernetes provider (GCE in this case)
318
+ it('create infrastructure buttons are enabled for apps with a cloud provider that does not have its ad-hoc operation disabled', () => {
319
+ let provider = '';
320
+ hasValue = true;
321
+ let isDisabled_result = false;
322
+ const k8s_account = fakeAccount('kubernetes');
323
+ k8s_account.type = 'kubernetes';
324
+ accounts = [k8s_account, fakeAccount('gce')];
325
+ let kubernetes_configuration = {
326
+ name: 'Kubernetes',
327
+ kubernetesAdHocInfraWritesEnabled: false,
328
+ };
329
+ CloudProviderRegistry.registerProvider('kubernetes', kubernetes_configuration);
330
+ CloudProviderRegistry.registerProvider('gce', config);
331
+ ProviderSelectionService.selectProvider(application, 'securityGroup').then((_provider) => {
332
+ provider = _provider;
333
+ });
334
+ ProviderSelectionService.isDisabled(application).then((isDisable) => {
335
+ isDisabled_result = isDisable;
336
+ });
337
+ $scope.$digest();
338
+ expect(isDisabled_result).toBe(false);
339
+ expect(provider).toBe('gce');
340
+ });
341
+
342
+ // If an application is configured to have kubernetes as a cloud provider, and there are multiple kubernetes accounts, then the create
343
+ // infrastructure buttons are disabled if kubernetesAdHocInfraWritesEnabled is false
344
+ it('create infrastructure buttons are disabled when all accounts have cloud providers with ad-hoc operations disabled', () => {
345
+ let isDisabled_result = false;
346
+ hasValue = true;
347
+ const k8s_account_1 = fakeAccount('kubernetes');
348
+ k8s_account_1.type = 'kubernetes';
349
+ const k8s_account_2 = fakeAccount('kubernetes');
350
+ k8s_account_2.type = 'kubernetes';
351
+
352
+ accounts = [k8s_account_1, k8s_account_2];
353
+ let configuration = {
354
+ name: 'kubernetes',
355
+ kubernetesAdHocInfraWritesEnabled: false,
356
+ };
357
+ CloudProviderRegistry.registerProvider('kubernetes', configuration);
358
+ ProviderSelectionService.isDisabled(application).then((isDisable) => {
359
+ isDisabled_result = isDisable;
360
+ });
361
+ $scope.$digest();
362
+ expect(isDisabled_result).toBe(true);
363
+ });
364
+ });
269
365
  });
@@ -53,14 +53,12 @@ export class ProviderSelectionService {
53
53
  public static isDisabled(app: Application): PromiseLike<boolean> {
54
54
  return AccountService.applicationAccounts(app).then((accounts: IAccountDetails[]) => {
55
55
  let isDisable = false;
56
- if (accounts.length === 1) {
57
- accounts
58
- .filter((a) => {
59
- return CloudProviderRegistry.hasValue(a.cloudProvider, 'kubernetesAdHocInfraWritesEnabled');
60
- })
61
- .map((a) => {
62
- isDisable = !CloudProviderRegistry.getValue(a.cloudProvider, 'kubernetesAdHocInfraWritesEnabled');
63
- });
56
+ const cloudProvidersEnabled = accounts.filter((a) => {
57
+ return !CloudProviderRegistry.isDisabled(a.cloudProvider);
58
+ });
59
+
60
+ if (cloudProvidersEnabled.length === 0) {
61
+ isDisable = true;
64
62
  }
65
63
  return isDisable;
66
64
  });
@@ -6,6 +6,7 @@
6
6
  class="btn btn-xs btn-default"
7
7
  ng-class="{active: filterModel.sortFilter.multiselect}"
8
8
  ng-click="ctrl.toggleMultiselect()"
9
+ ng-hide="isDisabled"
9
10
  >
10
11
  <span class="glyphicon glyphicon-list visible-lg-inline"></span>
11
12
  <span class="glyphicon glyphicon-list visible-md-inline visible-sm-inline" uib-tooltip="Edit multiple"></span>
@@ -73,6 +73,7 @@ export interface IManagedDeliveryURLs {
73
73
  pinning: string;
74
74
  resourceStatus: string;
75
75
  markAsBad: string;
76
+ previewEnvironments?: string;
76
77
  }
77
78
 
78
79
  export interface ISpinnakerSettings {
@@ -157,6 +158,7 @@ export const SETTINGS: ISpinnakerSettings = (window as any).spinnakerSettings ||
157
158
  // Make sure to set up some reasonable default settings fields so we do not have to keep checking if they exist everywhere
158
159
  SETTINGS.feature = SETTINGS.feature || {};
159
160
  SETTINGS.feature.roscoMode = SETTINGS.feature.roscoMode ?? true;
161
+ SETTINGS.kubernetesAdHocInfraWritesEnabled = SETTINGS.kubernetesAdHocInfraWritesEnabled ?? true;
160
162
  SETTINGS.analytics = SETTINGS.analytics || {};
161
163
  SETTINGS.providers = SETTINGS.providers || {};
162
164
  SETTINGS.defaultTimeZone = SETTINGS.defaultTimeZone || 'America/Los_Angeles';
@@ -3,6 +3,7 @@
3
3
  import UIROUTER_ANGULARJS from '@uirouter/angularjs';
4
4
  import { module } from 'angular';
5
5
 
6
+ import { ProviderSelectionService } from '../../cloudProvider/providerSelection/ProviderSelectionService';
6
7
  import { ConfirmationModalService } from '../../confirmationModal';
7
8
  import { InstanceWriter } from '../instance.write.service';
8
9
  import { CORE_INSTANCE_DETAILS_MULTIPLEINSTANCESERVERGROUP_DIRECTIVE } from './multipleInstanceServerGroup.directive';
@@ -20,6 +21,11 @@ module(CORE_INSTANCE_DETAILS_MULTIPLEINSTANCES_CONTROLLER, [
20
21
  'app',
21
22
  function ($scope, $state, app) {
22
23
  this.selectedGroups = [];
24
+ this.$onInit = () => {
25
+ ProviderSelectionService.isDisabled(app).then((disabled) => {
26
+ $scope.isDisabled = disabled;
27
+ });
28
+ };
23
29
 
24
30
  /**
25
31
  * Actions
@@ -16,6 +16,7 @@
16
16
  type="button"
17
17
  class="btn btn-sm btn-primary dropdown-toggle"
18
18
  ng-disabled="disabled"
19
+ ng-hide="isDisabled"
19
20
  uib-dropdown-toggle
20
21
  >
21
22
  Actions <span class="caret"></span>
@@ -65,10 +65,16 @@ interface IEnvironmentsRenderProps {
65
65
  children?: React.ReactNode;
66
66
  }
67
67
 
68
+ export interface OrderedEnvironments<T> {
69
+ className?: string;
70
+ style?: React.CSSProperties;
71
+ environments: T[];
72
+ }
73
+
68
74
  export const useOrderedEnvironment = <T extends object>(
69
75
  ref: React.RefObject<HTMLDivElement>,
70
76
  environments: T[],
71
- ): { className?: string; style?: React.CSSProperties; environments: T[] } => {
77
+ ): OrderedEnvironments<T> => {
72
78
  const { direction } = useEnvironmentDirectionState();
73
79
  const { width } = useDimensions(ref, { isActive: direction === 'gridView' });
74
80
 
@@ -120,6 +120,7 @@ export interface MdConfig {
120
120
  updatedAt?: Maybe<Scalars['InstantTime']>;
121
121
  rawConfig?: Maybe<Scalars['String']>;
122
122
  processedConfig?: Maybe<Scalars['String']>;
123
+ previewEnvironmentsConfigured?: Maybe<Scalars['Boolean']>;
123
124
  }
124
125
 
125
126
  export interface MdConstraint {
@@ -545,6 +546,7 @@ export type FetchApplicationQueryVariables = Exact<{
545
546
  export type FetchApplicationQuery = { __typename?: 'Query' } & {
546
547
  application?: Maybe<
547
548
  { __typename?: 'MdApplication' } & Pick<MdApplication, 'id' | 'name' | 'account'> & {
549
+ config?: Maybe<{ __typename?: 'MdConfig' } & Pick<MdConfig, 'id' | 'previewEnvironmentsConfigured'>>;
548
550
  environments: Array<
549
551
  { __typename?: 'MdEnvironment' } & Pick<MdEnvironment, 'isDeleting'> & {
550
552
  state: { __typename?: 'MdEnvironmentState' } & Pick<MdEnvironmentState, 'id'> & {
@@ -937,6 +939,10 @@ export const FetchApplicationDocument = gql`
937
939
  id
938
940
  name
939
941
  account
942
+ config {
943
+ id
944
+ previewEnvironmentsConfigured
945
+ }
940
946
  environments {
941
947
  ...baseEnvironmentFields
942
948
  isDeleting
@@ -31,6 +31,7 @@ type MdConfig {
31
31
  updatedAt: InstantTime
32
32
  rawConfig: String
33
33
  processedConfig: String
34
+ previewEnvironmentsConfigured: Boolean
34
35
  }
35
36
 
36
37
  type MdEnvironment {
@@ -2,6 +2,7 @@ import React from 'react';
2
2
 
3
3
  import { ApplicationQueryError } from '../ApplicationQueryError';
4
4
  import { EnvironmentOverview } from './EnvironmentOverview';
5
+ import { PreviewEnvironments } from './PreviewEnvironments';
5
6
  import { EnvironmentsRender, useOrderedEnvironment } from '../environmentBaseElements/EnvironmentsRender';
6
7
  import { useFetchApplicationQuery } from '../graphql/graphql-sdk';
7
8
  import { Messages } from '../messages/Messages';
@@ -25,11 +26,13 @@ export const EnvironmentsOverview = () => {
25
26
  environments.filter((env) => !env.isPreview),
26
27
  );
27
28
 
28
- const { environments: previewEnvironments, ...previewEnvironmentsProps } = useOrderedEnvironment(
29
+ const previewEnvironments = useOrderedEnvironment(
29
30
  wrapperRef,
30
31
  environments.filter((env) => env.isPreview),
31
32
  );
32
33
 
34
+ const previewEnvironmentsConfigured = data?.application?.config?.previewEnvironmentsConfigured;
35
+
33
36
  let content;
34
37
  if (loading && !data) {
35
38
  content = <Spinner {...spinnerProps} message="Loading environments..." />;
@@ -46,16 +49,10 @@ export const EnvironmentsOverview = () => {
46
49
  <EnvironmentOverview key={env.name} environment={env} />
47
50
  ))}
48
51
  </EnvironmentsRender>
49
- {Boolean(previewEnvironments.length) && (
50
- <h4 className="sp-margin-2xl-top sp-margin-m-bottom self-left">
51
- <b>Preview Environments</b>
52
- </h4>
53
- )}
54
- <EnvironmentsRender {...previewEnvironmentsProps}>
55
- {previewEnvironments.map((env) => (
56
- <EnvironmentOverview key={env.name} environment={env} />
57
- ))}
58
- </EnvironmentsRender>
52
+ <PreviewEnvironments
53
+ orderedEnvironments={previewEnvironments}
54
+ isConfigured={previewEnvironmentsConfigured}
55
+ />
59
56
  </>
60
57
  ) : (
61
58
  <div className="error-message">No environments found</div>
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+
3
+ import { EnvironmentOverview } from './EnvironmentOverview';
4
+ import { EnvironmentsRender, OrderedEnvironments } from '../environmentBaseElements/EnvironmentsRender';
5
+ import { QueryEnvironment } from './types';
6
+ import { getDocsUrl } from '../utils/defaults';
7
+ import { useLogEvent } from '../utils/logging';
8
+
9
+ interface IPreviewEnvironmentsProps {
10
+ orderedEnvironments: OrderedEnvironments<QueryEnvironment>;
11
+ isConfigured?: boolean;
12
+ }
13
+
14
+ export const PreviewEnvironments = ({ orderedEnvironments, isConfigured }: IPreviewEnvironmentsProps) => {
15
+ const { environments: previewEnvironments, ...previewEnvironmentsProps } = orderedEnvironments;
16
+ const logClick = useLogEvent('PreviewEnvironments');
17
+ const docsLink = getDocsUrl('previewEnvironments');
18
+ const hasActivePreviewEnvironments = previewEnvironments.length > 0;
19
+
20
+ return (
21
+ <>
22
+ {(isConfigured || hasActivePreviewEnvironments) && (
23
+ <h4 className="sp-margin-2xl-top sp-margin-m-bottom self-left">
24
+ <b>Preview Environments</b>
25
+ </h4>
26
+ )}
27
+ {isConfigured && !hasActivePreviewEnvironments && (
28
+ <div className="self-left">
29
+ No PRs matching your branch filter were found.
30
+ <br />
31
+ Your preview environments will automatically appear here once you create a PR from a branch matching your
32
+ config definition.{' '}
33
+ {docsLink && (
34
+ <a target="_blank" onClick={() => logClick({ action: 'Learn More' })} href={docsLink}>
35
+ Learn More
36
+ </a>
37
+ )}
38
+ </div>
39
+ )}
40
+ <EnvironmentsRender {...previewEnvironmentsProps}>
41
+ {previewEnvironments.map((env) => (
42
+ <EnvironmentOverview key={env.name} environment={env} />
43
+ ))}
44
+ </EnvironmentsRender>
45
+ </>
46
+ );
47
+ };
@@ -95,6 +95,10 @@ query fetchApplication($appName: String!, $statuses: [MdArtifactStatusInEnvironm
95
95
  id
96
96
  name
97
97
  account
98
+ config {
99
+ id
100
+ previewEnvironmentsConfigured
101
+ }
98
102
  environments {
99
103
  ...baseEnvironmentFields
100
104
  isDeleting
@@ -26,6 +26,6 @@ const DOCS_URLS: IManagedDeliveryURLs = {
26
26
  resourceStatus: 'https://www.spinnaker.io/guides/user/managed-delivery/resource-status/',
27
27
  };
28
28
 
29
- export const getDocsUrl = (doc: keyof IManagedDeliveryURLs): string => {
29
+ export const getDocsUrl = (doc: keyof IManagedDeliveryURLs): string | undefined => {
30
30
  return SETTINGS.managedDelivery?.urls?.[doc] || DOCS_URLS[doc];
31
31
  };
@@ -3,6 +3,7 @@
3
3
  import UIROUTER_ANGULARJS from '@uirouter/angularjs';
4
4
  import * as angular from 'angular';
5
5
 
6
+ import { ProviderSelectionService } from '../../cloudProvider/providerSelection/ProviderSelectionService';
6
7
  import { PROVIDER_SERVICE_DELEGATE } from '../../cloudProvider/providerService.delegate';
7
8
  import { ConfirmationModalService } from '../../confirmationModal';
8
9
  import { CORE_SERVERGROUP_DETAILS_MULTIPLESERVERGROUP_COMPONENT } from './multipleServerGroup.component';
@@ -27,6 +28,11 @@ angular
27
28
  'app',
28
29
  function ($scope, $state, serverGroupWriter, providerServiceDelegate, app) {
29
30
  this.serverGroups = [];
31
+ this.$onInit = () => {
32
+ ProviderSelectionService.isDisabled(app).then((disabled) => {
33
+ $scope.isDisabled = disabled;
34
+ });
35
+ };
30
36
 
31
37
  /**
32
38
  * Actions
@@ -16,6 +16,7 @@
16
16
  type="button"
17
17
  class="btn btn-sm btn-primary dropdown-toggle"
18
18
  ng-disabled="disabled"
19
+ ng-hide="isDisabled"
19
20
  uib-dropdown-toggle
20
21
  >
21
22
  Actions <span class="caret"></span>
@@ -93,7 +93,7 @@ export class CopyToClipboard extends React.Component<ICopyToClipboardProps, ICop
93
93
 
94
94
  const persistOverlay = Boolean(tooltipCopy);
95
95
  const copy = tooltipCopy || toolTip;
96
- const id = `clipboardValue-${text.replace(' ', '-')}`;
96
+ const id = `clipboardValue-${text.toString().replace(' ', '-')}`;
97
97
  const tooltipComponent = <Tooltip id={id}>{copy}</Tooltip>;
98
98
 
99
99
  // Hack - shouldUpdatePosition is a valid prop, just not declared in typings