@spinnaker/core 0.29.1 → 2025.0.1

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.
@@ -46,7 +46,7 @@ export interface IConstraintHandler<K = string> {
46
46
  };
47
47
  }
48
48
  declare class ConstraintsManager extends BasePluginManager<IConstraintHandler> {
49
- getIcon(constraint: IConstraint | IBaseConstraint): "placeholder" | "menu" | "add" | "done" | "environment" | "artifact" | "config" | "close" | "play" | "bake" | "plus" | "accordionCollapse" | "accordionExpand" | "accordionExpandAll" | "artifactApproved" | "artifactBad" | "artifactPending" | "artifactSkipped" | "build" | "buildFail" | "buildSuccess" | "canaryConfig" | "canaryFail" | "canaryRunning" | "canaryPass" | "canaryMarginal" | "caretRight" | "checkBadge" | "checkboxIndeterminate" | "checkboxChecked" | "checkboxUnchecked" | "closeSmall" | "cloudDeployed" | "cloudError" | "cloudProgress" | "cloudDecommissioned" | "cloudWaiting" | "cluster" | "configJ" | "configM" | "configS" | "copyClipboard" | "duplicate" | "edit" | "fileJson" | "fn" | "fnNew" | "formDrag" | "formError" | "formInfo" | "formNetworkBad" | "formNetworkGood" | "formRefresh" | "formWarning" | "heart" | "history" | "instances" | "loadBalancer" | "manualJudgement" | "manualJudgementApproved" | "manualJudgementRejected" | "mdActuating" | "mdActuationLaunched" | "mdCreated" | "mdDelay" | "mdDeltaDetected" | "mdDeltaResolved" | "mdDiff" | "mdError" | "mdUnhappy" | "mdPaused" | "mdResumed" | "mdUnknown" | "mdConstraintGeneric" | "mdConstraintDependsOn" | "mdConstraintAllowedTimes" | "md" | "mdVerification" | "menuClose" | "minus" | "pin" | "resourceT" | "securityGroup" | "servergroupAws" | "spCIBranch" | "spCIBuild" | "spCICommit" | "spCIMaster" | "spCIMerged" | "spCIPullRequest" | "spCIPullRequestClosed" | "spEnvironments" | "spMenuAppInSync" | "spMenuAppUnsynced" | "spMenuCanaryConfig" | "spMenuCanaryReport" | "spMenuClusters" | "spMenuConfig" | "spMenuFunctions" | "spMenuK8s" | "spMenuLoadBalancers" | "spMenuMeme" | "spMenuPager" | "spMenuPipelines" | "spMenuProperties" | "spMenuSecurityGroups" | "spMenuTasks" | "spMenuTimeline" | "spMenuZuul" | "spel" | "templateFull" | "templateS" | "templateWorkflow" | "toggleOff" | "toggleOn" | "trash" | "unpin";
49
+ getIcon(constraint: IConstraint | IBaseConstraint): "placeholder" | "menu" | "close" | "play" | "add" | "done" | "environment" | "artifact" | "config" | "bake" | "plus" | "accordionCollapse" | "accordionExpand" | "accordionExpandAll" | "artifactApproved" | "artifactBad" | "artifactPending" | "artifactSkipped" | "build" | "buildFail" | "buildSuccess" | "canaryConfig" | "canaryFail" | "canaryRunning" | "canaryPass" | "canaryMarginal" | "caretRight" | "checkBadge" | "checkboxIndeterminate" | "checkboxChecked" | "checkboxUnchecked" | "closeSmall" | "cloudDeployed" | "cloudError" | "cloudProgress" | "cloudDecommissioned" | "cloudWaiting" | "cluster" | "configJ" | "configM" | "configS" | "copyClipboard" | "duplicate" | "edit" | "fileJson" | "fn" | "fnNew" | "formDrag" | "formError" | "formInfo" | "formNetworkBad" | "formNetworkGood" | "formRefresh" | "formWarning" | "heart" | "history" | "instances" | "loadBalancer" | "manualJudgement" | "manualJudgementApproved" | "manualJudgementRejected" | "mdActuating" | "mdActuationLaunched" | "mdCreated" | "mdDelay" | "mdDeltaDetected" | "mdDeltaResolved" | "mdDiff" | "mdError" | "mdUnhappy" | "mdPaused" | "mdResumed" | "mdUnknown" | "mdConstraintGeneric" | "mdConstraintDependsOn" | "mdConstraintAllowedTimes" | "md" | "mdVerification" | "menuClose" | "minus" | "pin" | "resourceT" | "securityGroup" | "servergroupAws" | "spCIBranch" | "spCIBuild" | "spCICommit" | "spCIMaster" | "spCIMerged" | "spCIPullRequest" | "spCIPullRequestClosed" | "spEnvironments" | "spMenuAppInSync" | "spMenuAppUnsynced" | "spMenuCanaryConfig" | "spMenuCanaryReport" | "spMenuClusters" | "spMenuConfig" | "spMenuFunctions" | "spMenuK8s" | "spMenuLoadBalancers" | "spMenuMeme" | "spMenuPager" | "spMenuPipelines" | "spMenuProperties" | "spMenuSecurityGroups" | "spMenuTasks" | "spMenuTimeline" | "spMenuZuul" | "spel" | "templateFull" | "templateS" | "templateWorkflow" | "toggleOff" | "toggleOn" | "trash" | "unpin";
50
50
  renderTitle(constraint: IConstraint): React.ReactNode;
51
51
  hasContent(constraint: IConstraint): boolean;
52
52
  renderDescription(constraint: IConstraint): React.ReactNode;
@@ -13,7 +13,7 @@ export interface IResourceLinkProps {
13
13
  displayName?: string;
14
14
  }
15
15
  declare class ResourcesManager extends BasePluginManager<IResourceKindConfig> {
16
- getIcon(kind: string): "placeholder" | "menu" | "add" | "done" | "environment" | "artifact" | "config" | "close" | "play" | "bake" | "plus" | "accordionCollapse" | "accordionExpand" | "accordionExpandAll" | "artifactApproved" | "artifactBad" | "artifactPending" | "artifactSkipped" | "build" | "buildFail" | "buildSuccess" | "canaryConfig" | "canaryFail" | "canaryRunning" | "canaryPass" | "canaryMarginal" | "caretRight" | "checkBadge" | "checkboxIndeterminate" | "checkboxChecked" | "checkboxUnchecked" | "closeSmall" | "cloudDeployed" | "cloudError" | "cloudProgress" | "cloudDecommissioned" | "cloudWaiting" | "cluster" | "configJ" | "configM" | "configS" | "copyClipboard" | "duplicate" | "edit" | "fileJson" | "fn" | "fnNew" | "formDrag" | "formError" | "formInfo" | "formNetworkBad" | "formNetworkGood" | "formRefresh" | "formWarning" | "heart" | "history" | "instances" | "loadBalancer" | "manualJudgement" | "manualJudgementApproved" | "manualJudgementRejected" | "mdActuating" | "mdActuationLaunched" | "mdCreated" | "mdDelay" | "mdDeltaDetected" | "mdDeltaResolved" | "mdDiff" | "mdError" | "mdUnhappy" | "mdPaused" | "mdResumed" | "mdUnknown" | "mdConstraintGeneric" | "mdConstraintDependsOn" | "mdConstraintAllowedTimes" | "md" | "mdVerification" | "menuClose" | "minus" | "pin" | "resourceT" | "securityGroup" | "servergroupAws" | "spCIBranch" | "spCIBuild" | "spCICommit" | "spCIMaster" | "spCIMerged" | "spCIPullRequest" | "spCIPullRequestClosed" | "spEnvironments" | "spMenuAppInSync" | "spMenuAppUnsynced" | "spMenuCanaryConfig" | "spMenuCanaryReport" | "spMenuClusters" | "spMenuConfig" | "spMenuFunctions" | "spMenuK8s" | "spMenuLoadBalancers" | "spMenuMeme" | "spMenuPager" | "spMenuPipelines" | "spMenuProperties" | "spMenuSecurityGroups" | "spMenuTasks" | "spMenuTimeline" | "spMenuZuul" | "spel" | "templateFull" | "templateS" | "templateWorkflow" | "toggleOff" | "toggleOn" | "trash" | "unpin";
16
+ getIcon(kind: string): "placeholder" | "menu" | "close" | "play" | "add" | "done" | "environment" | "artifact" | "config" | "bake" | "plus" | "accordionCollapse" | "accordionExpand" | "accordionExpandAll" | "artifactApproved" | "artifactBad" | "artifactPending" | "artifactSkipped" | "build" | "buildFail" | "buildSuccess" | "canaryConfig" | "canaryFail" | "canaryRunning" | "canaryPass" | "canaryMarginal" | "caretRight" | "checkBadge" | "checkboxIndeterminate" | "checkboxChecked" | "checkboxUnchecked" | "closeSmall" | "cloudDeployed" | "cloudError" | "cloudProgress" | "cloudDecommissioned" | "cloudWaiting" | "cluster" | "configJ" | "configM" | "configS" | "copyClipboard" | "duplicate" | "edit" | "fileJson" | "fn" | "fnNew" | "formDrag" | "formError" | "formInfo" | "formNetworkBad" | "formNetworkGood" | "formRefresh" | "formWarning" | "heart" | "history" | "instances" | "loadBalancer" | "manualJudgement" | "manualJudgementApproved" | "manualJudgementRejected" | "mdActuating" | "mdActuationLaunched" | "mdCreated" | "mdDelay" | "mdDeltaDetected" | "mdDeltaResolved" | "mdDiff" | "mdError" | "mdUnhappy" | "mdPaused" | "mdResumed" | "mdUnknown" | "mdConstraintGeneric" | "mdConstraintDependsOn" | "mdConstraintAllowedTimes" | "md" | "mdVerification" | "menuClose" | "minus" | "pin" | "resourceT" | "securityGroup" | "servergroupAws" | "spCIBranch" | "spCIBuild" | "spCICommit" | "spCIMaster" | "spCIMerged" | "spCIPullRequest" | "spCIPullRequestClosed" | "spEnvironments" | "spMenuAppInSync" | "spMenuAppUnsynced" | "spMenuCanaryConfig" | "spMenuCanaryReport" | "spMenuClusters" | "spMenuConfig" | "spMenuFunctions" | "spMenuK8s" | "spMenuLoadBalancers" | "spMenuMeme" | "spMenuPager" | "spMenuPipelines" | "spMenuProperties" | "spMenuSecurityGroups" | "spMenuTasks" | "spMenuTimeline" | "spMenuZuul" | "spel" | "templateFull" | "templateS" | "templateWorkflow" | "toggleOff" | "toggleOn" | "trash" | "unpin";
17
17
  getExperimentalDisplayLink(resource: IResourceLinkProps): string | undefined;
18
18
  getSpinnakerType(kind: string): string;
19
19
  getNativeResourceRoutingInfo({ kind, account, stack, detail, displayName, }: IResourceLinkProps): {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@spinnaker/core",
3
3
  "license": "Apache-2.0",
4
- "version": "0.29.1",
4
+ "version": "2025.0.1",
5
5
  "module": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
7
7
  "publishConfig": {
8
- "access": "public"
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org"
9
10
  },
10
11
  "scripts": {
11
12
  "clean": "shx rm -rf dist",
@@ -123,5 +124,5 @@
123
124
  "shx": "0.3.3",
124
125
  "typescript": "4.3.5"
125
126
  },
126
- "gitHead": "2f7f8de7b0c43ea29ac505a9b4c0f5d9b29456f4"
127
+ "gitHead": "ad8bab4aea2c6174eeb6c5e20614f15d26313a7a"
127
128
  }
@@ -53,6 +53,7 @@ export interface IFeatures {
53
53
  functions?: boolean;
54
54
  kubernetesRawResources?: boolean;
55
55
  renderPipelineStageThreshold?: number;
56
+ deployManifestStageAdvancedConfiguration?: boolean;
56
57
  }
57
58
 
58
59
  export interface IDockerInsightSettings {
@@ -127,6 +128,7 @@ export interface ISpinnakerSettings {
127
128
  manifestBasePath: string;
128
129
  urls?: Partial<IManagedDeliveryURLs>;
129
130
  };
131
+ maxFetchHistoryOnEvaluateVariables?: number;
130
132
  maxPipelineAgeDays: number;
131
133
  newApplicationDefaults: INewApplicationDefaults;
132
134
  notifications: INotificationSettings;
@@ -175,6 +177,7 @@ SETTINGS.managedDelivery = SETTINGS.managedDelivery || {
175
177
  defaultManifest: 'spinnaker.yml',
176
178
  manifestBasePath: '.spinnaker',
177
179
  };
180
+ SETTINGS.maxFetchHistoryOnEvaluateVariables = SETTINGS.maxFetchHistoryOnEvaluateVariables ?? 100;
178
181
 
179
182
  // A helper to make resetting settings to steady state after running tests easier
180
183
  const originalSettings: ISpinnakerSettings = cloneDeep(SETTINGS);
@@ -11,7 +11,11 @@ export interface IMetricAlarmDimension {
11
11
 
12
12
  interface IDataPoint {
13
13
  timestamp: number;
14
- average: number;
14
+ average?: number;
15
+ sum?: number;
16
+ minimum?: number;
17
+ maximum?: number;
18
+ sampleCount?: number;
15
19
  }
16
20
 
17
21
  export interface ICloudMetricStatistics {
@@ -7,6 +7,7 @@ import { CORE_FORMS_CHECKLIST_CHECKLIST_DIRECTIVE } from './checklist/checklist.
7
7
  import { CORE_FORMS_CHECKMAP_CHECKMAP_DIRECTIVE } from './checkmap/checkmap.directive';
8
8
  import { CORE_FORMS_IGNOREEMPTYDELETE_DIRECTIVE } from './ignoreEmptyDelete.directive';
9
9
  import { CORE_FORMS_MAPEDITOR_MAPEDITOR_COMPONENT } from './mapEditor/mapEditor.component';
10
+ import { CORE_FORMS_MAPOBJECTEDITOR_MAPOBJECTEDITOR_COMPONENT } from './mapObjectEditor/mapObjectEditor.component';
10
11
  import { NUMBER_LIST_COMPONENT } from './numberList/numberList.component';
11
12
  import { CORE_FORMS_VALIDATEONSUBMIT_VALIDATEONSUBMIT_DIRECTIVE } from './validateOnSubmit/validateOnSubmit.directive';
12
13
 
@@ -18,6 +19,7 @@ module(CORE_FORMS_FORMS_MODULE, [
18
19
  CORE_FORMS_CHECKMAP_CHECKMAP_DIRECTIVE,
19
20
  CORE_FORMS_IGNOREEMPTYDELETE_DIRECTIVE,
20
21
  CORE_FORMS_MAPEDITOR_MAPEDITOR_COMPONENT,
22
+ CORE_FORMS_MAPOBJECTEDITOR_MAPOBJECTEDITOR_COMPONENT,
21
23
  CORE_FORMS_VALIDATEONSUBMIT_VALIDATEONSUBMIT_DIRECTIVE,
22
24
  NUMBER_LIST_COMPONENT,
23
25
  ]);
@@ -0,0 +1,56 @@
1
+ <form name="mapObjectEditor">
2
+ <div class="sm-label-left" ng-if="$ctrl.label">
3
+ <b>{{ $ctrl.label }}</b>
4
+ </div>
5
+ <input class="form-control input-sm" ng-model="$ctrl.model" ng-if="$ctrl.isParameterized" />
6
+ <table class="table table-condensed packed tags {{ $ctrl.tableClass }}" ng-if="!$ctrl.isParameterized">
7
+ <thead>
8
+ <tr ng-if="!$ctrl.labelsLeft">
9
+ <th ng-bind="$ctrl.keyLabel"></th>
10
+ <th ng-bind="$ctrl.valueLabel"></th>
11
+ <th></th>
12
+ </tr>
13
+ </thead>
14
+ <tbody>
15
+ <tr ng-repeat="pair in $ctrl.backingModel" ng-if="!$ctrl.hiddenKeys.includes(pair.key)">
16
+ <td class="table-label" ng-if="$ctrl.labelsLeft">
17
+ <b>{{ $ctrl.keyLabel }}</b>
18
+ </td>
19
+ <td>
20
+ <input
21
+ class="form-control input input-sm"
22
+ type="text"
23
+ name="{{ $index }}"
24
+ ng-model="pair.key"
25
+ validate-unique="pair.checkUnique"
26
+ />
27
+ <div class="error-message" ng-if="mapObjectEditor[$index].$error.validateUnique">Duplicate key</div>
28
+ </td>
29
+ <td class="table-label" ng-if="$ctrl.labelsLeft">
30
+ <b>{{ $ctrl.valueLabel }}</b>
31
+ </td>
32
+ <td>
33
+ <textarea json-text class="form-control input input-sm" ng-model="pair.value" rows="4"></textarea>
34
+ </td>
35
+ <td>
36
+ <div class="form-control-static">
37
+ <a href ng-click="$ctrl.removeField($index)">
38
+ <span class="glyphicon glyphicon-trash"></span>
39
+ <span class="sr-only">Remove field</span>
40
+ </a>
41
+ </div>
42
+ </td>
43
+ </tr>
44
+ </tbody>
45
+ <tfoot>
46
+ <tr>
47
+ <td colspan="{{ $ctrl.columnCount }}">
48
+ <button class="btn btn-block btn-sm add-new" ng-click="$ctrl.addField()">
49
+ <span class="glyphicon glyphicon-plus-sign"></span>
50
+ {{ $ctrl.addButtonLabel }}
51
+ </button>
52
+ </td>
53
+ </tr>
54
+ </tfoot>
55
+ </table>
56
+ </form>
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ import * as angular from 'angular';
4
+ import { isString } from 'lodash';
5
+
6
+ import { CORE_VALIDATION_VALIDATEUNIQUE_DIRECTIVE } from '../../validation/validateUnique.directive';
7
+
8
+ import './mapObjectEditor.component.less';
9
+
10
+ export const CORE_FORMS_MAPOBJECTEDITOR_MAPOBJECTEDITOR_COMPONENT = 'spinnaker.core.forms.mapObjectEditor.component';
11
+ export const name = CORE_FORMS_MAPOBJECTEDITOR_MAPOBJECTEDITOR_COMPONENT; // for backwards compatibility
12
+ angular
13
+ .module(CORE_FORMS_MAPOBJECTEDITOR_MAPOBJECTEDITOR_COMPONENT, [CORE_VALIDATION_VALIDATEUNIQUE_DIRECTIVE])
14
+ .directive('jsonText', function () {
15
+ return {
16
+ restrict: 'A',
17
+ require: 'ngModel',
18
+ link: function (scope, element, attr, ngModel) {
19
+ function into(input) {
20
+ return JSON.parse(input);
21
+ }
22
+ function out(data) {
23
+ return JSON.stringify(data, null, 2);
24
+ }
25
+ ngModel.$parsers.push(into);
26
+ ngModel.$formatters.push(out);
27
+ },
28
+ };
29
+ })
30
+ .component('mapObjectEditor', {
31
+ bindings: {
32
+ model: '=',
33
+ keyLabel: '@',
34
+ valueLabel: '@',
35
+ addButtonLabel: '@',
36
+ allowEmpty: '=?',
37
+ onChange: '&',
38
+ labelsLeft: '<?',
39
+ label: '@',
40
+ hiddenKeys: '<',
41
+ },
42
+ controller: [
43
+ '$scope',
44
+ function ($scope) {
45
+ this.backingModel = [];
46
+
47
+ const modelKeys = () => Object.keys(this.model);
48
+
49
+ this.addField = () => {
50
+ this.backingModel.push({ key: '', value: {}, checkUnique: modelKeys() });
51
+ // do not fire the onChange event, since no values have been committed to the object
52
+ };
53
+
54
+ this.removeField = (index) => {
55
+ this.backingModel.splice(index, 1);
56
+ this.synchronize();
57
+ this.onChange();
58
+ };
59
+
60
+ // Clears existing values from model, then replaces them
61
+ this.synchronize = () => {
62
+ if (this.isParameterized) {
63
+ return;
64
+ }
65
+ const modelStart = JSON.stringify(this.model);
66
+ const allKeys = this.backingModel.map((pair) => pair.key);
67
+ modelKeys().forEach((key) => delete this.model[key]);
68
+ this.backingModel.forEach((pair) => {
69
+ if (pair.key && (this.allowEmpty || pair.value)) {
70
+ try {
71
+ // Parse value if it is a valid JSON object
72
+ this.model[pair.key] = JSON.parse(pair.value);
73
+ } catch (e) {
74
+ // If value is not a valid JSON object, just store the raw value
75
+ this.model[pair.key] = pair.value;
76
+ }
77
+ }
78
+ // include other keys to verify no duplicates
79
+ pair.checkUnique = allKeys.filter((key) => pair.key !== key);
80
+ });
81
+ if (modelStart !== JSON.stringify(this.model)) {
82
+ this.onChange();
83
+ }
84
+ };
85
+
86
+ // In Angular 1.7 Directive bindings were removed in the constructor, default values now must be instantiated within $onInit
87
+ // See https://docs.angularjs.org/guide/migration#-compile- and https://docs.angularjs.org/guide/migration#migrate1.5to1.6-ng-services-$compile
88
+ this.$onInit = () => {
89
+ // Set default values for optional fields
90
+ this.onChange = this.onChange || angular.noop;
91
+ this.keyLabel = this.keyLabel || 'Key';
92
+ this.valueLabel = this.valueLabel || 'Value';
93
+ this.addButtonLabel = this.addButtonLabel || 'Add Field';
94
+ this.allowEmpty = this.allowEmpty || false;
95
+ this.labelsLeft = this.labelsLeft || false;
96
+ this.tableClass = this.label ? '' : 'no-border-top';
97
+ this.columnCount = this.labelsLeft ? 5 : 3;
98
+ this.model = this.model || {};
99
+ this.isParameterized = isString(this.model);
100
+ this.hiddenKeys = this.hiddenKeys || [];
101
+
102
+ if (this.model && !this.isParameterized) {
103
+ modelKeys().forEach((key) => {
104
+ this.backingModel.push({ key: key, value: this.model[key] });
105
+ });
106
+ }
107
+ };
108
+
109
+ $scope.$watch(() => JSON.stringify(this.backingModel), this.synchronize);
110
+ },
111
+ ],
112
+ templateUrl: require('./mapObjectEditor.component.html'),
113
+ });
@@ -0,0 +1,9 @@
1
+ map-object-editor {
2
+ .table.no-border-top {
3
+ border-top: 2px solid var(--color-white);
4
+
5
+ .table-label {
6
+ padding: 0.8rem 0 0 1rem;
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ describe('Component: mapObjectEditor', function () {
4
+ var scope;
5
+
6
+ beforeEach(window.module(require('./mapObjectEditor.component').name));
7
+
8
+ beforeEach(
9
+ window.inject(function ($rootScope, $compile) {
10
+ scope = $rootScope.$new();
11
+ this.compile = $compile;
12
+ }),
13
+ );
14
+
15
+ it('initializes with provided values', function () {
16
+ scope.model = { foo: { bar: 'baz' }, bah: 11 };
17
+ let dom = this.compile('<map-object-editor model="model"></map-object-editor>')(scope);
18
+ scope.$digest();
19
+
20
+ expect(dom.find('input').length).toBe(2);
21
+ expect(dom.find('textarea').length).toBe(2);
22
+
23
+ expect(dom.find('input').get(0).value).toBe('foo');
24
+ expect(dom.find('textarea').get(0).value).toBe(JSON.stringify({ bar: 'baz' }, null, 2));
25
+ expect(dom.find('input').get(1).value).toBe('bah');
26
+ expect(dom.find('textarea').get(1).value).toBe('11');
27
+ });
28
+
29
+ describe('adding new entries', function () {
30
+ it('creates a new row in the table, but does not synchronize to model', function () {
31
+ scope.model = {};
32
+ let dom = this.compile('<map-object-editor model="model"></map-object-editor>')(scope);
33
+ scope.$digest();
34
+ dom.find('button').click();
35
+ expect(dom.find('tbody tr').length).toBe(1);
36
+ expect(dom.find('input').length).toBe(1);
37
+ expect(dom.find('textarea').length).toBe(1);
38
+ });
39
+
40
+ it('does not flag multiple new rows without keys as having duplicate keys', function () {
41
+ scope.model = {};
42
+ let dom = this.compile('<map-object-editor model="model"></map-object-editor>')(scope);
43
+ scope.$digest();
44
+ dom.find('button').click();
45
+ dom.find('button').click();
46
+
47
+ expect(dom.find('tbody tr').length).toBe(2);
48
+ expect(dom.find('input').length).toBe(2);
49
+ expect(dom.find('textarea').length).toBe(2);
50
+
51
+ expect(dom.find('.error-message').length).toBe(0);
52
+ });
53
+ });
54
+
55
+ describe('removing entries', function () {
56
+ it('removes the entry when the trash can is clicked', function () {
57
+ scope.model = { foo: { bar: 'baz' } };
58
+ let dom = this.compile('<map-object-editor model="model"></map-object-editor>')(scope);
59
+ scope.$digest();
60
+
61
+ expect(dom.find('input').length).toBe(1);
62
+ expect(dom.find('textarea').length).toBe(1);
63
+
64
+ dom.find('a').click();
65
+
66
+ expect(dom.find('tbody tr').length).toBe(0);
67
+ expect(dom.find('input').length).toBe(0);
68
+ expect(dom.find('textarea').length).toBe(0);
69
+ expect(scope.model.foo).toBeUndefined();
70
+ });
71
+ });
72
+
73
+ describe('duplicate key handling', function () {
74
+ it('provides a warning when a duplicate key is entered', function () {
75
+ scope.model = { a: { bar: 'baz' }, b: '2' };
76
+ let dom = this.compile('<map-object-editor model="model"></map-object-editor>')(scope);
77
+ scope.$digest();
78
+
79
+ $(dom.find('input')[1]).val('a').trigger('input');
80
+ scope.$digest();
81
+
82
+ expect(dom.find('.error-message').length).toBe(1);
83
+ });
84
+ });
85
+ });
@@ -2,6 +2,7 @@ import { isEqual, keyBy } from 'lodash';
2
2
  import React from 'react';
3
3
  import type { Option } from 'react-select';
4
4
 
5
+ import { SETTINGS } from '../../../../config';
5
6
  import type { IExecution, IPipeline, IStage } from '../../../../domain';
6
7
  import type { IStageForSpelPreview } from '../../../../presentation';
7
8
  import { FormField, ReactSelectInput, useData } from '../../../../presentation';
@@ -23,7 +24,10 @@ export function ExecutionAndStagePicker(props: IExecutionAndStagePickerProps) {
23
24
  const [showStageSelector, setShowStageSelector] = React.useState(false);
24
25
 
25
26
  const fetchExecutions = useData(
26
- () => executionService.getExecutionsForConfigIds([pipeline.id], { limit: 100 }),
27
+ () =>
28
+ executionService.getExecutionsForConfigIds([pipeline.id], {
29
+ limit: SETTINGS.maxFetchHistoryOnEvaluateVariables,
30
+ }),
27
31
  [],
28
32
  [],
29
33
  );
@@ -104,6 +104,17 @@
104
104
  <button class="self-right passive" ng-click="pipelineStageCtrl.removeInvalidParameters()">Remove all</button>
105
105
  </div>
106
106
  </div>
107
+ <div ng-if="hasSpeLDefinedParameterBlock()" class="horizontal center sp-margin-l-top" style="width: 100%">
108
+ <div class="warning-text alert-info vertical">
109
+ <p>
110
+ <i class="fa fa-exclamation-triangle"></i>
111
+ The pipelineParameters is defined as a SpeL expression and will be fully evaluated on Runtime.
112
+ </p>
113
+ <p>
114
+ <code ng-bind-html="stage.pipelineParameters"></code>
115
+ </p>
116
+ </div>
117
+ </div>
107
118
  </div>
108
119
  <stage-config-field label="Skip downstream output" help-key="pipeline.skipDownstreamOutput">
109
120
  <input type="checkbox" class="input-sm" name="skipDownstreamOutput" ng-model="stage.skipDownstreamOutput" />
@@ -70,7 +70,12 @@ module(CORE_PIPELINE_CONFIG_STAGES_PIPELINE_PIPELINESTAGE, [])
70
70
  $scope.applications = _.map(applications, 'name').sort();
71
71
  initializeMasters();
72
72
  });
73
-
73
+ const isExpression =
74
+ $scope.stage.pipelineParameters !== undefined &&
75
+ $scope.stage.pipelineParameters !== null &&
76
+ typeof $scope.stage.pipelineParameters === 'string'
77
+ ? $scope.stage.pipelineParameters.startsWith('${') && $scope.stage.pipelineParameters.endsWith('}')
78
+ : false;
74
79
  function initializeMasters() {
75
80
  if ($scope.stage.application && !$scope.stage.application.includes('${')) {
76
81
  PipelineConfigService.getPipelinesForApplication($scope.stage.application).then(function (pipelines) {
@@ -130,12 +135,14 @@ module(CORE_PIPELINE_CONFIG_STAGES_PIPELINE_PIPELINESTAGE, [])
130
135
  (value, name) => !acceptedPipelineParams.includes(name),
131
136
  );
132
137
  }
133
-
134
- $scope.hasInvalidParameters = () => Object.keys($scope.invalidParameters || {}).length;
138
+ $scope.hasSpeLDefinedParameterBlock = () => isExpression;
139
+ $scope.hasInvalidParameters = () => Object.keys(($scope.invalidParameters && !isExpression) || {}).length;
135
140
  $scope.useDefaultParameters = {};
136
141
  _.each($scope.pipelineParameters, function (property) {
137
- if (!(property.name in $scope.stage.pipelineParameters) && property.default !== null) {
138
- $scope.useDefaultParameters[property.name] = true;
142
+ if (!isExpression) {
143
+ if (!(property.name in $scope.stage.pipelineParameters) && property.default !== null) {
144
+ $scope.useDefaultParameters[property.name] = true;
145
+ }
139
146
  }
140
147
  });
141
148
  } else {