Skip to content

Commit

Permalink
refactor(artifacts): Combine expected artifacts and trigger artifact …
Browse files Browse the repository at this point in the history
…constraints (spinnaker#6634)

To enable new artifact functional set `feature.artifactsRewrite = true` in Deck. 

Co-Authored-By: Scott Bloch-Wehba-Seaward <sbws@google.com>
  • Loading branch information
jkschneider and Scott Bloch-Wehba-Seaward authored Mar 5, 2019
1 parent 2224c5e commit 5da2965
Show file tree
Hide file tree
Showing 77 changed files with 2,458 additions and 812 deletions.
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/artifact/ArtifactTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export const ArtifactTypePatterns = {
IVY_FILE: /ivy\/file/,
MAVEN_FILE: /maven\/file/,
HTTP_FILE: /http\/file/,
FRONT50_PIPELINE_TEMPLATE: /front50\/pipelineTemplate/,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Registry } from 'core/registry';
describe('ExpectedArtifactService', () => {
describe('getKindConfig()', () => {
const baseKindConfig = {
typePattern: /base-type/,
label: '',
description: '',
isDefault: false,
Expand All @@ -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,
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
///<reference path="./human-readable-ids.d.ts" />

import { PipelineConfigService } from 'core/pipeline';
import { Registry } from 'core/registry';
import {
IPipeline,
IStage,
IExpectedArtifact,
IExecutionContext,
IArtifact,
IArtifactSource,
IArtifactKindConfig,
IArtifactSource,
IExecutionContext,
IExpectedArtifact,
IPipeline,
IStage,
} from 'core/domain';
import { UUIDGenerator } from 'core/utils';
import { hri as HumanReadableIds } from 'human-readable-ids';
Expand Down
7 changes: 7 additions & 0 deletions app/scripts/modules/core/src/artifact/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ export * from './NgAppEngineDeployArtifactDelegate';
export * from './NgBakeManifestArtifactDelegate';
export * from './IArtifactAccountPair';
export * from './NgAppengineConfigArtifactDelegate';
export * from './react/ArtifactAccountSelector';
export * from './react/ArtifactIcon';
export * from './react/ExpectedArtifactSelector';
export * from './react/ExpectedArtifactEditor';
export * from './react/ExpectedArtifactSourceSelector';
export * from './react/ExpectedArtifactModal';
export * from './react/StageArtifactSelector';
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,38 @@ import { react2angular } from 'react2angular';
import { IArtifactAccount } from 'core/account';
import { TetheredSelect } from 'core/presentation';

import { ArtifactIcon } from './ArtifactIcon';

export interface IArtifactAccountSelectorProps {
accounts: IArtifactAccount[];
selected: IArtifactAccount;
onChange: (_: IArtifactAccount) => void;
onChange: (account: IArtifactAccount) => void;
className?: string;
}

export interface IArtifactAccountSelectorOption {
value: string;
label: string;
}

export class ArtifactAccountSelector extends React.Component<IArtifactAccountSelectorProps> {
constructor(props: IArtifactAccountSelectorProps) {
super(props);
}

private onChange = (option: IArtifactAccountSelectorOption) => {
const account = this.props.accounts.find(a => a.name === option.value);
this.props.onChange(account);
private renderOption = (account: IArtifactAccount) => {
return (
<span>
<ArtifactIcon type={account.types[0]} width="16" height="16" />
{account.name}
</span>
);
};

public render() {
const options = this.props.accounts.map(a => ({ value: a.name, label: a.name }));
const value = this.props.selected ? { value: this.props.selected.name, label: this.props.selected.name } : null;
return (
<TetheredSelect
className={this.props.className || ''}
options={options}
value={value}
onChange={this.onChange}
options={this.props.accounts}
value={this.props.selected}
onChange={this.props.onChange}
optionRenderer={this.renderOption}
valueRenderer={this.renderOption}
clearable={false}
/>
);
Expand Down
57 changes: 57 additions & 0 deletions app/scripts/modules/core/src/artifact/react/ArtifactEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import * as React from 'react';
import { cloneDeep } from 'lodash';
import { IArtifactAccount } from 'core/account';
import { ArtifactAccountSelector } from 'core/artifact';
import { IArtifact, IPipeline } from 'core/domain';
import { StageConfigField } from 'core/pipeline/config/stages/common';
import { Registry } from 'core/registry';
import { UUIDGenerator } from 'core/utils';

export interface IArtifactEditorProps {
pipeline: IPipeline;
artifact: IArtifact;
artifactAccounts: IArtifactAccount[];
onArtifactEdit: (artifact: IArtifact) => void;
isDefault: boolean;
}

/**
* Editor for either the match or default side of an expected artifact. Also
* used in stages where an inline default artifact may be defined.
*/
export class ArtifactEditor extends React.Component<IArtifactEditorProps> {
private onArtifactAccountChanged = (artifactAccount: IArtifactAccount) => {
// reset artifact fields if the account type has changed, so we don't leave dangling properties
// that are not modifiable using the new artifact account type's form.
const artifact: IArtifact =
this.props.artifact && artifactAccount.types.includes(this.props.artifact.type)
? cloneDeep(this.props.artifact)
: { id: UUIDGenerator.generateUuid() };
artifact.artifactAccount = artifactAccount.name;
this.props.onArtifactEdit(artifact);
};

public render(): React.ReactNode {
const { pipeline, artifact, artifactAccounts, onArtifactEdit, isDefault } = this.props;
const artifactAccount = artifactAccounts.find(acc => acc.name === artifact.artifactAccount) || artifactAccounts[0];
const accountTypes = artifactAccount ? artifactAccount.types : undefined;
const kinds = isDefault ? Registry.pipeline.getDefaultArtifactKinds() : Registry.pipeline.getMatchArtifactKinds();
const kind = accountTypes ? kinds.find(a => accountTypes.some(typ => a.typePattern.test(typ))) : undefined;
const EditCmp = kind && kind.editCmp;

return (
<>
<StageConfigField label="Account" fieldColumns={8}>
<ArtifactAccountSelector
accounts={artifactAccounts}
selected={artifactAccount}
onChange={this.onArtifactAccountChanged}
/>
</StageConfigField>
{EditCmp && (
<EditCmp account={artifactAccount} artifact={artifact} onChange={onArtifactEdit} pipeline={pipeline} />
)}
</>
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as React from 'react';
import { react2angular } from 'react2angular';
import { cloneDeep } from 'lodash';

import { IExpectedArtifact, IArtifactKindConfig, IArtifact } from 'core/domain';
import { IExpectedArtifact, IArtifactKindConfig, IArtifact, IPipeline } from 'core/domain';
import { IArtifactAccount } from 'core/account';
import { StageConfigField } from 'core/pipeline';

Expand All @@ -24,13 +24,13 @@ export interface IExpectedArtifactEditorProps {
kinds: IArtifactKindConfig[];
sources: IExpectedArtifactSourceOption[];
accounts: IArtifactAccount[];
onArtifactChange?: (_a: IArtifact) => void;
onSave: (e: IExpectedArtifactEditorSaveEvent) => void;
showIcons?: boolean;
showAccounts?: boolean;
hidePipelineFields?: boolean;
className?: string;
fieldGroupClassName?: string;
fieldColumns: number;
singleColumn: boolean;
pipeline?: IPipeline;
}

interface IExpectedArtifactEditorState {
Expand Down Expand Up @@ -58,10 +58,11 @@ export class ExpectedArtifactEditor extends React.Component<

constructor(props: IExpectedArtifactEditorProps) {
super(props);
const expectedArtifact = props.default ? cloneDeep(props.default) : ExpectedArtifactService.createEmptyArtifact();
this.state = {
expectedArtifact: props.default ? cloneDeep(props.default) : ExpectedArtifactService.createEmptyArtifact(),
expectedArtifact: expectedArtifact,
source: props.sources[0],
account: null,
account: this.accountsForExpectedArtifact(expectedArtifact)[0],
};
}

Expand All @@ -85,13 +86,6 @@ export class ExpectedArtifactEditor extends React.Component<
this.setState({ source: e });
};

public onDisplayNameEdit = (e: React.FormEvent<HTMLInputElement>) => {
const newName = e.currentTarget.value;
const expectedArtifact = { ...this.state.expectedArtifact };
expectedArtifact.displayName = newName;
this.setState({ expectedArtifact });
};

private onKindChange = (kind: IArtifactKindConfig) => {
const expectedArtifact = cloneDeep(this.state.expectedArtifact);
expectedArtifact.matchArtifact.type = kind.type;
Expand All @@ -103,6 +97,14 @@ export class ExpectedArtifactEditor extends React.Component<
};

private onAccountChange = (account: IArtifactAccount) => {
const { expectedArtifact } = this.state;
this.props.onArtifactChange &&
this.props.onArtifactChange({
artifactAccount: account.name,
id: expectedArtifact.matchArtifact.id,
reference: expectedArtifact.matchArtifact.name,
type: expectedArtifact.matchArtifact.type,
});
this.setState({ account });
};

Expand All @@ -113,6 +115,13 @@ export class ExpectedArtifactEditor extends React.Component<
const accounts = this.accountsForExpectedArtifact(expectedArtifact);
account = accounts[0];
}
this.props.onArtifactChange &&
this.props.onArtifactChange({
artifactAccount: account ? account.name : '',
id: artifact.id,
reference: artifact.name,
type: artifact.type,
});
this.setState({ expectedArtifact, account });
};

Expand All @@ -126,8 +135,18 @@ export class ExpectedArtifactEditor extends React.Component<
}
};

private onArtifactDisplayNameEdit = (event: React.ChangeEvent<HTMLInputElement>): void => {
const displayName = event.target.value;
const { expectedArtifact } = this.state;
const newExpectedArtifact = {
...expectedArtifact,
displayName,
};
this.setState({ expectedArtifact: newExpectedArtifact });
};

public render() {
const { sources, showIcons, showAccounts, fieldColumns, singleColumn, fieldGroupClassName } = this.props;
const { sources, showIcons, showAccounts, hidePipelineFields } = this.props;
const { expectedArtifact, source, account } = this.state;
const accounts = this.accountsForExpectedArtifact(expectedArtifact);
const artifact = ExpectedArtifactService.artifactFromExpected(expectedArtifact);
Expand All @@ -136,17 +155,21 @@ export class ExpectedArtifactEditor extends React.Component<
const EditCmp = kind && kind.editCmp;
return (
<>
<StageConfigField label="Display Name" fieldColumns={fieldColumns} groupClassName={fieldGroupClassName}>
<input
className="form-control input-sm"
value={expectedArtifact.displayName}
onChange={this.onDisplayNameEdit}
/>
</StageConfigField>
<StageConfigField label="Artifact Source" fieldColumns={fieldColumns} groupClassName={fieldGroupClassName}>
<ExpectedArtifactSourceSelector sources={sources} selected={source} onChange={this.onSourceChange} />
</StageConfigField>
<StageConfigField label="Artifact Kind" fieldColumns={fieldColumns} groupClassName={fieldGroupClassName}>
{!hidePipelineFields && (
<StageConfigField label="Display Name">
<input
className="form-control"
value={expectedArtifact.displayName}
onChange={this.onArtifactDisplayNameEdit}
/>
</StageConfigField>
)}
{sources.length > 1 && (
<StageConfigField label="Artifact Source">
<ExpectedArtifactSourceSelector sources={sources} selected={source} onChange={this.onSourceChange} />
</StageConfigField>
)}
<StageConfigField label="Artifact Kind">
<ExpectedArtifactKindSelector
kinds={kinds}
selected={kind}
Expand All @@ -155,7 +178,7 @@ export class ExpectedArtifactEditor extends React.Component<
/>
</StageConfigField>
{showAccounts && (
<StageConfigField label="Artifact Account" fieldColumns={fieldColumns} groupClassName={fieldGroupClassName}>
<StageConfigField label="Artifact Account">
<ArtifactAccountSelector accounts={accounts} selected={account} onChange={this.onAccountChange} />
</StageConfigField>
)}
Expand All @@ -164,17 +187,16 @@ export class ExpectedArtifactEditor extends React.Component<
account={account}
artifact={artifact}
onChange={this.onArtifactEdit}
labelColumns={3}
fieldColumns={fieldColumns}
singleColumn={singleColumn}
groupClassName={fieldGroupClassName}
pipeline={this.props.pipeline}
/>
)}
<StageConfigField label="" fieldColumns={fieldColumns} groupClassName={fieldGroupClassName}>
<button onClick={this.onSave} className="btn btn-block btn-primary btn-sm">
Confirm
</button>
</StageConfigField>
{!hidePipelineFields && (
<StageConfigField label="">
<button onClick={this.onSave} type="button" className="btn btn-block btn-primary btn-sm">
Confirm
</button>
</StageConfigField>
)}
</>
);
}
Expand All @@ -195,8 +217,6 @@ module(EXPECTED_ARTIFACT_EDITOR_COMPONENT_REACT, [
'showIcons',
'showAccounts',
'className',
'fieldColumns',
'singleColumn',
'fieldGroupClassName',
'pipeline',
]),
);
Loading

0 comments on commit 5da2965

Please sign in to comment.