@spinnaker/core 0.29.0 → 2025.0.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.
- package/CHANGELOG.md +11 -0
- package/dist/config/settings.d.ts +2 -0
- package/dist/deploymentStrategy/strategies/redblack/AdditionalFields.d.ts +9 -2
- package/dist/domain/ICloudMetric.d.ts +5 -1
- package/dist/forms/mapObjectEditor/mapObjectEditor.component.d.ts +2 -0
- package/dist/index.js +71738 -328
- package/dist/index.js.map +1 -1
- package/dist/managed/constraints/registry.d.ts +1 -1
- package/dist/managed/resources/resourceRegistry.d.ts +1 -1
- package/package.json +4 -3
- package/src/config/settings.ts +3 -0
- package/src/deploymentStrategy/strategies/redblack/AdditionalFields.tsx +97 -68
- package/src/domain/ICloudMetric.ts +5 -1
- package/src/forms/forms.module.js +2 -0
- package/src/forms/mapObjectEditor/mapObjectEditor.component.html +56 -0
- package/src/forms/mapObjectEditor/mapObjectEditor.component.js +113 -0
- package/src/forms/mapObjectEditor/mapObjectEditor.component.less +9 -0
- package/src/forms/mapObjectEditor/mapObjectEditor.component.spec.js +85 -0
- package/src/pipeline/config/stages/evaluateVariables/ExecutionAndStagePicker.tsx +5 -1
- package/src/pipeline/config/stages/pipeline/pipelineStage.html +11 -0
- package/src/pipeline/config/stages/pipeline/pipelineStage.js +12 -5
|
@@ -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" | "
|
|
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" | "
|
|
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.
|
|
4
|
+
"version": "2025.0.0",
|
|
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": "
|
|
127
|
+
"gitHead": "5b197da8614979a51dc24b0a6f92e19c3162e874"
|
|
127
128
|
}
|
package/src/config/settings.ts
CHANGED
|
@@ -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);
|
|
@@ -8,72 +8,101 @@ export interface IRedBlackStrategyAdditionalFieldsProps extends IDeploymentStrat
|
|
|
8
8
|
command: IRedBlackCommand;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
11
|
+
export class AdditionalFields extends React.Component<IRedBlackStrategyAdditionalFieldsProps> {
|
|
12
|
+
private rollbackOnFailureChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
13
|
+
this.props.command.rollback.onFailure = e.target.checked;
|
|
14
|
+
this.forceUpdate();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
private scaleDownChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
18
|
+
this.props.command.scaleDown = e.target.checked;
|
|
19
|
+
this.forceUpdate();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
private maxRemainingAsgsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
23
|
+
this.props.command.maxRemainingAsgs = parseInt(e.target.value, 10);
|
|
24
|
+
this.forceUpdate();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
private delayBeforeDisableSecChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
28
|
+
this.props.command.delayBeforeDisableSec = parseInt(e.target.value, 10);
|
|
29
|
+
this.forceUpdate();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
private delayBeforeScaleDownSecChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
33
|
+
this.props.command.delayBeforeScaleDownSec = parseInt(e.target.value, 10);
|
|
34
|
+
this.forceUpdate();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
public render() {
|
|
38
|
+
const { command } = this.props;
|
|
39
|
+
return (
|
|
40
|
+
<div className="form-group">
|
|
41
|
+
<div className="col-md-12 checkbox" style={{ marginTop: 0 }}>
|
|
42
|
+
<label>
|
|
43
|
+
<input
|
|
44
|
+
type="checkbox"
|
|
45
|
+
checked={command.rollback?.onFailure ?? false}
|
|
46
|
+
onChange={this.rollbackOnFailureChange}
|
|
47
|
+
/>
|
|
48
|
+
Rollback to previous server group if deployment fails <HelpField id="strategy.redblack.rollback" />
|
|
49
|
+
</label>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="col-md-12 checkbox">
|
|
52
|
+
<label>
|
|
53
|
+
<input type="checkbox" checked={command.scaleDown} onChange={this.scaleDownChange} />
|
|
54
|
+
Scale down replaced server groups to zero instances <HelpField id="strategy.redblack.scaleDown" />
|
|
55
|
+
</label>
|
|
56
|
+
</div>
|
|
57
|
+
<div className="col-md-12 form-inline">
|
|
58
|
+
<label>
|
|
59
|
+
Maximum number of server groups to leave
|
|
60
|
+
<HelpField id="strategy.redblack.maxRemainingAsgs" />
|
|
61
|
+
</label>
|
|
62
|
+
<input
|
|
63
|
+
className="form-control input-sm"
|
|
64
|
+
style={{ width: '50px' }}
|
|
65
|
+
type="number"
|
|
66
|
+
value={command.maxRemainingAsgs}
|
|
67
|
+
onChange={this.maxRemainingAsgsChange}
|
|
68
|
+
min="2"
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="col-md-12 form-inline">
|
|
72
|
+
<label>
|
|
73
|
+
Wait Before Disable
|
|
74
|
+
<HelpField content="Time to wait before disabling all old server groups in this cluster" />
|
|
75
|
+
</label>
|
|
76
|
+
<input
|
|
77
|
+
className="form-control input-sm"
|
|
78
|
+
style={{ width: '60px' }}
|
|
79
|
+
min="0"
|
|
80
|
+
type="number"
|
|
81
|
+
value={command.delayBeforeDisableSec}
|
|
82
|
+
onChange={this.delayBeforeDisableSecChange}
|
|
83
|
+
placeholder="0"
|
|
84
|
+
/>
|
|
85
|
+
seconds
|
|
86
|
+
</div>
|
|
87
|
+
{command.scaleDown && (
|
|
88
|
+
<div className="col-md-12 form-inline" style={{ marginTop: '5px' }}>
|
|
89
|
+
<label>
|
|
90
|
+
Wait Before Scale Down
|
|
91
|
+
<HelpField content="Time to wait before scaling down all old server groups in this cluster" />
|
|
92
|
+
</label>
|
|
93
|
+
<input
|
|
94
|
+
className="form-control input-sm"
|
|
95
|
+
style={{ width: '60px' }}
|
|
96
|
+
min="0"
|
|
97
|
+
type="number"
|
|
98
|
+
value={command.delayBeforeScaleDownSec}
|
|
99
|
+
onChange={this.delayBeforeScaleDownSecChange}
|
|
100
|
+
placeholder="0"
|
|
101
|
+
/>
|
|
102
|
+
seconds
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
76
105
|
</div>
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -11,7 +11,11 @@ export interface IMetricAlarmDimension {
|
|
|
11
11
|
|
|
12
12
|
interface IDataPoint {
|
|
13
13
|
timestamp: number;
|
|
14
|
-
average
|
|
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,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
|
-
() =>
|
|
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 (!
|
|
138
|
-
$scope.
|
|
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 {
|