import { cloneDeep } from 'lodash'; import React from 'react'; import type { Option } from 'react-select'; import { ArtifactService } from '../ArtifactService'; import { ArtifactTypePatterns } from '../../../../../artifact/ArtifactTypes'; import type { IArtifact, IArtifactEditorProps, IArtifactKindConfig } from '../../../../../domain'; import { TetheredCreatable, TetheredSelect } from '../../../../../presentation'; import { StageConfigField } from '../../../stages/common'; import { Spinner } from '../../../../../widgets/spinners/Spinner'; const TYPE = 'helm/image'; interface IHelmImageArtifactEditorState { names: string[]; versions: Array>; versionsLoading: boolean; namesLoading: boolean; } class HelmImageEditor extends React.Component { public state: IHelmImageArtifactEditorState = { names: [], versions: [], versionsLoading: true, namesLoading: true, }; // taken from https://github.com/semver/semver/issues/232 private SEMVER = new RegExp( /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/, ); constructor(props: IArtifactEditorProps) { super(props); const { artifact } = this.props; if (artifact.type !== TYPE) { const clonedArtifact = cloneDeep(artifact); clonedArtifact.type = TYPE; props.onChange(clonedArtifact); } ArtifactService.getArtifactNames(TYPE, this.props.account.name).then( (names) => { this.setState({ names, namesLoading: false, }); }, () => { this.setState({ names: [], namesLoading: false, versionsLoading: false, versions: [], }); }, ); } public componentDidMount() { const { artifact } = this.props; if (artifact.name) { this.getChartVersionOptions(artifact.name); } } public componentDidUpdate(prevProps: IArtifactEditorProps) { if (this.props.account.name !== prevProps.account.name) { ArtifactService.getArtifactNames(TYPE, this.props.account.name).then( (names) => { this.setState({ names, namesLoading: false, versions: [], }); }, () => { this.setState({ names: [], namesLoading: false, versions: [], }); }, ); } } public render() { const { artifact } = this.props; const nameOptions = this.state.names.map((name) => ({ value: name, label: name })); return ( <> {!this.state.namesLoading && ( { this.onChange(e, 'name'); this.getChartVersionOptions(e.value.toString()); }} clearable={false} /> )} {this.state.namesLoading && } {!this.state.versionsLoading && ( { this.onChange(e, 'version'); }} clearable={false} /> )} {this.state.versionsLoading && } ); } private onChange = (e: Option, field: keyof IArtifact) => { const clone = cloneDeep(this.props.artifact); (clone[field] as any) = e.value.toString(); this.props.onChange(clone); }; private getChartVersionOptions(chartName: string) { const { artifact, account } = this.props; this.setState({ versionsLoading: true }); ArtifactService.getArtifactVersions(TYPE, account.name, chartName).then((versions: string[]) => { // if the version doesn't match SEMVER we assume that it's a regular expression or SpEL expression // and add it to the list of valid versions if (artifact.version && !this.SEMVER.test(artifact.version)) { versions = versions.concat(artifact.version); } this.setState({ versions: versions.map((v) => ({ label: v, value: v })), versionsLoading: false, }); }); } } export const HelmImageMatch: IArtifactKindConfig = { label: 'Helm', typePattern: ArtifactTypePatterns.HELM_IMAGE, type: TYPE, isDefault: false, isMatch: true, description: 'A helm chart to be deployed', key: 'helm', editCmp: HelmImageEditor, }; export const HelmImageDefault: IArtifactKindConfig = { label: 'Helm', typePattern: ArtifactTypePatterns.HELM_IMAGE, type: TYPE, isDefault: true, isMatch: false, description: 'A helm chart to be deployed', key: 'default.helm', editCmp: HelmImageEditor, };