diff --git a/app/scripts/modules/core/src/artifact/ArtifactTypes.ts b/app/scripts/modules/core/src/artifact/ArtifactTypes.ts
index 97116185e13..102c5c51f17 100644
--- a/app/scripts/modules/core/src/artifact/ArtifactTypes.ts
+++ b/app/scripts/modules/core/src/artifact/ArtifactTypes.ts
@@ -12,4 +12,5 @@ export const ArtifactTypePatterns = {
IVY_FILE: /ivy\/file/,
MAVEN_FILE: /maven\/file/,
HTTP_FILE: /http\/file/,
+ FRONT50_PIPELINE_TEMPLATE: /front50\/pipelineTemplate/,
};
diff --git a/app/scripts/modules/core/src/artifact/expectedArtifact.service.spec.ts b/app/scripts/modules/core/src/artifact/expectedArtifact.service.spec.ts
index b7bb4e7e1e0..8947b359def 100644
--- a/app/scripts/modules/core/src/artifact/expectedArtifact.service.spec.ts
+++ b/app/scripts/modules/core/src/artifact/expectedArtifact.service.spec.ts
@@ -6,6 +6,7 @@ import { Registry } from 'core/registry';
describe('ExpectedArtifactService', () => {
describe('getKindConfig()', () => {
const baseKindConfig = {
+ typePattern: /base-type/,
label: '',
description: '',
isDefault: false,
@@ -15,21 +16,25 @@ describe('ExpectedArtifactService', () => {
};
const kindConfigs: IArtifactKindConfig[] = [
{
+ typePattern: /foo-type/,
type: 'foo-type',
key: 'foo',
isMatch: true,
},
{
+ typePattern: /foo-type/,
type: 'foo-type',
key: 'foo-default',
isDefault: true,
},
{
+ typePattern: /bar-type/,
type: 'bar-type',
key: 'bar',
isMatch: true,
},
{
+ typePattern: /bar-type/,
type: 'bar-type',
key: 'bar-default',
isDefault: true,
@@ -47,24 +52,6 @@ describe('ExpectedArtifactService', () => {
Registry.pipeline.registerCustomArtifactKind(customKindConfig);
});
- it('returns the custom kind when set as the artifact kind', () => {
- const artifact: IArtifact = {
- kind: 'custom',
- id: 'artifact-id',
- };
- const kindConfig = ExpectedArtifactService.getKindConfig(artifact, false);
- expect(kindConfig).toEqual(customKindConfig);
- });
-
- it('returns the custom kind when set as the artifact kind and isDefault is true', () => {
- const artifact: IArtifact = {
- kind: 'custom',
- id: 'artifact-id',
- };
- const kindConfig = ExpectedArtifactService.getKindConfig(artifact, true);
- expect(kindConfig).toEqual(customKindConfig);
- });
-
it('infers kind from type', () => {
const artifact: IArtifact = {
id: 'artifact-id',
diff --git a/app/scripts/modules/core/src/artifact/expectedArtifact.service.ts b/app/scripts/modules/core/src/artifact/expectedArtifact.service.ts
index c286e649c62..6b838f76248 100644
--- a/app/scripts/modules/core/src/artifact/expectedArtifact.service.ts
+++ b/app/scripts/modules/core/src/artifact/expectedArtifact.service.ts
@@ -1,15 +1,13 @@
-///
See the reference for more information.
`, + 'pipeline.config.expectedArtifact.usePriorExecution': ` +Attempt to match against an artifact in the prior pipeline execution's context. This ensures that you will always be using the most recently supplied artifact to this pipeline, and is generally a safe choice.
`, 'pipeline.config.expectedArtifact.defaultArtifact': `If your artifact either wasn't supplied from a trigger, or it wasn't found in a prior execution, the artifact specified below will end up in your pipeline's execution context.
See the reference for more information.
`, diff --git a/app/scripts/modules/core/src/pipeline/config/PipelineRegistry.ts b/app/scripts/modules/core/src/pipeline/config/PipelineRegistry.ts index 55898294653..fea7a6c6387 100644 --- a/app/scripts/modules/core/src/pipeline/config/PipelineRegistry.ts +++ b/app/scripts/modules/core/src/pipeline/config/PipelineRegistry.ts @@ -1,4 +1,4 @@ -import { uniq, isNil, cloneDeep, intersection, memoize } from 'lodash'; +import { uniq, isNil, cloneDeep, intersection, memoize, defaults } from 'lodash'; import { Application } from 'core/application/application.model'; import { @@ -8,12 +8,15 @@ import { IStageTypeConfig, IArtifactKindConfig, IStageOrTriggerTypeConfig, + IArtifactEditorProps, } from 'core/domain'; import { CloudProviderRegistry, ICloudProviderConfig } from 'core/cloudProvider'; import { SETTINGS } from 'core/config/settings'; import { IAccountDetails } from 'core/account/AccountService'; import { ITriggerTemplateComponentProps } from '../manualExecution/TriggerTemplate'; +import { ComponentType, SFC } from 'react'; +import { artifactKindConfigs } from './triggers/artifacts'; export interface ITransformer { transform: (application: Application, execution: IExecution) => void; @@ -23,7 +26,7 @@ export class PipelineRegistry { private triggerTypes: ITriggerTypeConfig[] = []; private stageTypes: IStageTypeConfig[] = []; private transformers: ITransformer[] = []; - private artifactKinds: IArtifactKindConfig[] = []; + private artifactKinds: IArtifactKindConfig[] = artifactKindConfigs; private customArtifactKind: IArtifactKindConfig; constructor() { @@ -80,8 +83,20 @@ export class PipelineRegistry { this.normalizeStageTypes(); } - public registerArtifactKind(artifactKindConfig: IArtifactKindConfig): void { + public registerArtifactKind( + artifactKindConfig: IArtifactKindConfig, + ): ComponentTypeDisplay name | +Actions | +
---|---|
{artifact.displayName} | ++ editExpectedArtifact(artifact)} /> + removeExpectedArtifact(artifact)} /> + | +
diff --git a/app/scripts/modules/core/src/task/monitor/TaskMonitor.ts b/app/scripts/modules/core/src/task/monitor/TaskMonitor.ts
index 64ff5764edb..a04b0fb6d18 100644
--- a/app/scripts/modules/core/src/task/monitor/TaskMonitor.ts
+++ b/app/scripts/modules/core/src/task/monitor/TaskMonitor.ts
@@ -56,7 +56,9 @@ export class TaskMonitor {
this.monitorInterval = config.monitorInterval || 1000;
this.submitMethod = config.submitMethod;
- this.modalInstance.result.then(() => this.onModalClose(), () => this.onModalClose());
+ if (this.modalInstance) {
+ this.modalInstance.result.then(() => this.onModalClose(), () => this.onModalClose());
+ }
}
public onModalClose(): void {
diff --git a/app/scripts/modules/core/src/utils/clipboard/CopyToClipboard.tsx b/app/scripts/modules/core/src/utils/clipboard/CopyToClipboard.tsx
index f0e643445a6..fb6678ecb18 100644
--- a/app/scripts/modules/core/src/utils/clipboard/CopyToClipboard.tsx
+++ b/app/scripts/modules/core/src/utils/clipboard/CopyToClipboard.tsx
@@ -9,6 +9,7 @@ export interface ICopyToClipboardProps {
displayText?: boolean;
text: string;
toolTip: string;
+ className?: string;
}
interface ICopyToClipboardState {
diff --git a/app/scripts/modules/core/src/utils/index.ts b/app/scripts/modules/core/src/utils/index.ts
index a179f0b800b..cdb72029c56 100644
--- a/app/scripts/modules/core/src/utils/index.ts
+++ b/app/scripts/modules/core/src/utils/index.ts
@@ -9,3 +9,4 @@ export * from './scrollTo/scrollTo.service';
export * from './timeFormatters';
export * from './uuid.service';
export * from './workerPool';
+export * from './renderIfFeature.component';
diff --git a/app/scripts/modules/core/src/widgets/index.ts b/app/scripts/modules/core/src/widgets/index.ts
index 6215a573fd9..826440544d8 100644
--- a/app/scripts/modules/core/src/widgets/index.ts
+++ b/app/scripts/modules/core/src/widgets/index.ts
@@ -8,3 +8,4 @@ export * from './spinners/Spinner';
export * from './ScopeClusterSelector';
export * from './tags';
export * from './spelText/SpelNumberInput';
+export * from './spelText/SpelText';
diff --git a/app/scripts/modules/core/src/widgets/spelText/SpelAutocompleteService.ts b/app/scripts/modules/core/src/widgets/spelText/SpelAutocompleteService.ts
new file mode 100644
index 00000000000..eec0c1e24f9
--- /dev/null
+++ b/app/scripts/modules/core/src/widgets/spelText/SpelAutocompleteService.ts
@@ -0,0 +1,420 @@
+import { ExecutionService } from '../../pipeline/service/execution.service';
+import { IPipeline, IExecution, IStage } from '../../domain';
+import { JsonListBuilder } from './JsonListBuilder';
+import { IPromise, IQService } from 'angular';
+
+interface IBracket {
+ open: string;
+ close: string;
+}
+
+interface ITextcompleteConfigElement {
+ id: string;
+
+ [k: string]: any;
+}
+
+interface IHelperParam {
+ name: string;
+ type: string;
+}
+
+interface IExecutionCache {
+ [k: string]: IExecution;
+}
+
+export class SpelAutocompleteService {
+ private executionCache: IExecutionCache = {};
+
+ private brackets: IBracket[] = [{ open: '(', close: ')' }, { open: '[', close: ']' }];
+
+ private quotes: IBracket[] = [{ open: "'", close: "'" }, { open: '"', close: '"' }];
+
+ private helperFunctions = [
+ 'alphanumerical',
+ 'readJson',
+ 'fromUrl',
+ 'propertiesFromUrl',
+ 'jsonFromUrl',
+ 'judgment',
+ 'stage',
+ 'toBoolean',
+ 'toFloat',
+ 'toInt',
+ 'toJson',
+ 'toBase64',
+ 'fromBase64',
+ ];
+
+ private helperParams = [
+ 'execution',
+ 'parameters',
+ 'trigger',
+ 'scmInfo',
+ 'scmInfo.sha1',
+ 'scmInfo.branch',
+ 'deployedServerGroups',
+ ];
+
+ private codedHelperParams: IHelperParam[] = this.helperParams.map((param: string) => {
+ return { name: param, type: 'param' };
+ });
+
+ public textcompleteConfig: ITextcompleteConfigElement[] = [
+ {
+ id: 'SpEL wrapper',
+ match: /\$(\w*)$/,
+ search: function(_: any, callback: (value: string[]) => void) {
+ callback(['${...}']);
+ },
+ index: 1,
+ replace: function replace() {
+ return ['${ ', ' }'];
+ },
+ },
+ {
+ id: 'match quotes',
+ match: /(["'])(\w*)$/,
+ index: 1,
+ search: (term: string, callback: (value: IBracket[]) => void) => {
+ callback(this.quotes.filter((bracket: IBracket) => bracket.open.indexOf(term) === 0));
+ },
+ template: function() {
+ return `'...'`;
+ },
+ replace: function replace() {
+ return [`'`, `'`];
+ },
+ },
+ {
+ id: 'match brackets',
+ match: /([\[('])(\w*)$/,
+ index: 1,
+ search: (term: string, callback: (value: IBracket[]) => void) => {
+ callback(this.brackets.filter((bracket: IBracket) => bracket.open.indexOf(term) === 0));
+ },
+ template: (value: IBracket) => {
+ return `${value.open}...${value.close}`;
+ },
+ replace: (bracket: IBracket) => {
+ return [`${bracket.open} `, ` ${bracket.close}`];
+ },
+ },
+ ];
+
+ constructor(private $q: IQService, private executionService: ExecutionService) {
+ 'ngInject';
+ }
+
+ private paramInList(checkParam: { name: string }) {
+ return (testParam: { name: string }) => checkParam.name === testParam.name;
+ }
+
+ private addToTextcompleteConfig(
+ configList: ITextcompleteConfigElement[],
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ const textcompleteConfigCopy = textcompleteConfig.slice(0);
+
+ for (const newConfig of configList) {
+ if (textcompleteConfig.filter(config => config.id === newConfig.id).length === 0) {
+ textcompleteConfigCopy.push(newConfig);
+ }
+ }
+
+ return textcompleteConfigCopy;
+ }
+
+ private listSearchFn(list: any) {
+ return (term: any, callback: any) => {
+ callback(
+ list.filter((item: any) => {
+ if (item.leaf.includes(term)) {
+ return item;
+ }
+ }),
+ );
+ };
+ }
+
+ private leafTemplateFn(stage: any) {
+ return `${
+ stage.leaf
+ } ${stage.value.length > 90 ? `${stage.value.slice(0, 90)}...` : stage.value}`;
+ }
+
+ private addExecutionForAutocomplete(execution: IExecution, textcompleteConfig: ITextcompleteConfigElement[]) {
+ if (execution) {
+ const executionList = JsonListBuilder.convertJsonKeysToBracketedList(execution, ['task']);
+ const configList = [
+ {
+ id: `execution: ${execution.id}`,
+ match: /execution(\w*|\s*)$/,
+ index: 1,
+ search: this.listSearchFn(executionList),
+ template: this.leafTemplateFn,
+ replace: (value: any) => {
+ return `execution${value.leaf}`;
+ },
+ },
+ ];
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+ return textcompleteConfig;
+ }
+
+ private addDeloyedServerGroupsForAutoComplete(
+ execution: IExecution,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (execution && execution.context && execution.context.deploymentDetails) {
+ const deployList = JsonListBuilder.convertJsonKeysToBracketedList(execution.context.deploymentDetails);
+ const configList = [
+ {
+ id: `deploymentServerGroups: ${execution.id}`,
+ match: /deployedServerGroups(\w*|\s*)$/,
+ index: 1,
+ search: this.listSearchFn(deployList),
+ template: this.leafTemplateFn,
+ replace: (value: any) => {
+ return `deployedServerGroups${value.leaf}`;
+ },
+ },
+ ];
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+ return textcompleteConfig;
+ }
+
+ private addStageDataForAutocomplete(
+ pipeline: IPipeline | IExecution,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.stages) {
+ const configList = (pipeline.stages as IStage[]).map(stage => {
+ const stageList = JsonListBuilder.convertJsonKeysToBracketedList(stage, ['task']);
+
+ return {
+ id: `stage config for ${stage.name}`,
+ match: new RegExp(`#stage\\(\\s*'${JsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
+ index: 1,
+ search: this.listSearchFn(stageList),
+ template: this.leafTemplateFn,
+ replace: (param: any) => {
+ return `#stage('${stage.name}')${param.leaf}`;
+ },
+ };
+ });
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+
+ return textcompleteConfig;
+ }
+
+ private addManualJudgementConfigForAutocomplete(
+ pipeline: IPipeline | IExecution,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.stages) {
+ const manualJudgementStageList = pipeline.stages.filter(stage => stage.type === 'manualJudgment');
+
+ const configList = manualJudgementStageList.map(stage => {
+ const stageList = JsonListBuilder.convertJsonKeysToBracketedList(stage);
+
+ return {
+ id: `judgement config for ${stage.name}`,
+ match: new RegExp(`#judgment\\(\\s*'\\s*${JsonListBuilder.escapeForRegEx(stage.name)}'\\s*\\)(.*)$`),
+ index: 1,
+ search: this.listSearchFn(stageList),
+ template: this.leafTemplateFn,
+ replace: (param: any) => {
+ return `#judgement('${stage.name}')${param.leaf}`;
+ },
+ };
+ });
+
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+
+ return textcompleteConfig;
+ }
+
+ private addTriggerConfigForAutocomplete(
+ pipeline: IExecution,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.trigger) {
+ const triggerAsList = [pipeline.trigger];
+ const configList = triggerAsList.map(trigger => {
+ const triggerInfoList = JsonListBuilder.convertJsonKeysToBracketedList(trigger);
+ return {
+ id: `trigger config: ${trigger.type}`,
+ match: /trigger(\w*|\s*)$/,
+ index: 1,
+ search: this.listSearchFn(triggerInfoList),
+ template: this.leafTemplateFn,
+ replace: (value: any) => {
+ return `trigger${value.leaf}`;
+ },
+ };
+ });
+
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+
+ return textcompleteConfig;
+ }
+
+ private addParameterConfigForAutocomplete(
+ pipeline: IPipeline,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.parameterConfig) {
+ const paramsAsList = [pipeline.parameterConfig];
+ const configList = paramsAsList.map(params => {
+ const paramsInfoList = JsonListBuilder.convertJsonKeysToBracketedList(params);
+ return {
+ id: `parameter config: ${Object.keys(params).join(',')}`,
+ match: /parameters(\w*|\s*)$/,
+ index: 1,
+ search: this.listSearchFn(paramsInfoList),
+ template: this.leafTemplateFn,
+ replace: (value: any) => {
+ return `parameters${value.leaf}`;
+ },
+ };
+ });
+
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+
+ return textcompleteConfig;
+ }
+
+ private addStageNamesToCodeHelperList(
+ pipeline: IExecution & IPipeline,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.stages) {
+ let codedHelperParamsCopy = this.codedHelperParams.slice(0);
+
+ const pipelineHasParameters = pipeline.parameterConfig && pipeline.parameterConfig.length;
+ codedHelperParamsCopy = pipelineHasParameters
+ ? codedHelperParamsCopy
+ : codedHelperParamsCopy.filter(param => param.name !== 'parameters');
+
+ const hasJenkinsTriggerOrStage =
+ (pipeline.trigger && pipeline.trigger.type === 'jenkins') ||
+ pipeline.stages.some(stage => stage.type === 'jenkins');
+ codedHelperParamsCopy = hasJenkinsTriggerOrStage
+ ? codedHelperParamsCopy
+ : codedHelperParamsCopy.filter(param => !param.name.includes('scmInfo'));
+
+ pipeline.stages.forEach(stage => {
+ const newParam = { name: stage.name, type: stage.type };
+ if (codedHelperParamsCopy.filter(this.paramInList(newParam)).length === 0) {
+ codedHelperParamsCopy.push({ name: stage.name, type: 'stage' });
+ }
+ });
+
+ const configList = [
+ {
+ id: 'params',
+ match: /(\s*|\w*)\?(\s*|\w*|')$/,
+ index: 2,
+ search: (term: string, callback: (value: IHelperParam[]) => void) => {
+ callback(
+ codedHelperParamsCopy.filter((param: any) => param.name.includes(term) || param.type.includes(term)),
+ );
+ },
+ template: (value: IHelperParam) => ` ${value.name}`,
+ replace: (param: IHelperParam) => `${param.name}`,
+ },
+ ];
+
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+ return textcompleteConfig;
+ }
+
+ private addHelperFunctionsBasedOnStages(
+ pipeline: IPipeline | IExecution,
+ textcompleteConfig: ITextcompleteConfigElement[],
+ ): ITextcompleteConfigElement[] {
+ if (pipeline && pipeline.stages) {
+ let helperFunctionsCopy = this.helperFunctions.slice(0);
+ const hasManualJudmentStage = pipeline.stages.some(stage => stage.type === 'manualJudgment');
+ if (!hasManualJudmentStage) {
+ helperFunctionsCopy = this.helperFunctions.filter(fnName => fnName !== 'judgment');
+ }
+
+ const configList = [
+ {
+ id: 'helper functions',
+ match: /#(\w*)$/,
+ index: 1,
+ search: (term: string, callback: (value: string[]) => void) => {
+ callback(helperFunctionsCopy.filter((helper: string) => helper.indexOf(term) === 0));
+ },
+ template: (value: string) => ` #${value}`,
+ replace: (helper: string) => (helper === 'toJson' ? [`#${helper}(`, ')'] : [`#${helper}( '`, `' )`]),
+ },
+ ];
+
+ return this.addToTextcompleteConfig(configList, textcompleteConfig);
+ }
+ return textcompleteConfig;
+ }
+
+ private getLastExecutionByPipelineConfig(pipelineConfig: IPipeline): IPromiseManifest Configuration
+