import { capitalize, get } from 'lodash'; import { $q } from 'ngimport'; import React from 'react'; import type { Option } from 'react-select'; import { from as observableFrom, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import type { BuildServiceType } from '../../../../ci'; import { IgorService } from '../../../../ci'; import type { IBuild, IBuildInfo, IBuildTrigger, IPipelineCommand } from '../../../../domain'; import { buildDisplayName } from '../../../executionBuild/buildDisplayName.filter'; import type { ITriggerTemplateComponentProps } from '../../../manualExecution/TriggerTemplate'; import { TextInput } from '../../../../presentation'; import { TetheredSelect } from '../../../../presentation/TetheredSelect'; import { timestamp } from '../../../../utils/timeFormatters'; import { Spinner } from '../../../../widgets/spinners/Spinner'; export interface IBaseBuildTriggerTemplateProps extends ITriggerTemplateComponentProps { buildTriggerType: BuildServiceType; optionRenderer?: (build: Option) => JSX.Element; } export interface IBaseBuildTriggerTemplateState { builds?: IBuild[]; buildsLoading?: boolean; loadError?: boolean; selectedBuild?: number; explicitBuild?: boolean; } export class BaseBuildTriggerTemplate extends React.Component< IBaseBuildTriggerTemplateProps, IBaseBuildTriggerTemplateState > { private destroy$ = new Subject(); public static formatLabel(trigger: IBuildTrigger): PromiseLike { return $q.when(`(${capitalize(trigger.type)}) ${trigger.master}: ${trigger.job}`); } public constructor(props: IBaseBuildTriggerTemplateProps) { super(props); this.state = { builds: [], buildsLoading: false, loadError: false, selectedBuild: 0, explicitBuild: false, }; } private buildLoadSuccess = (allBuilds: IBuild[]) => { const newState: Partial = { buildsLoading: false, }; const trigger = this.props.command.trigger as IBuildTrigger; newState.builds = (allBuilds || []) .filter((build) => !build.building && build.result === 'SUCCESS') .sort((a, b) => b.number - a.number); if (newState.builds.length) { // default to what is supplied by the trigger if possible; otherwise, use the latest const defaultSelection = newState.builds.find((b) => b.number === trigger.buildNumber) || newState.builds[0]; newState.selectedBuild = defaultSelection.number; this.updateSelectedBuild(defaultSelection); } this.setState(newState); }; private buildLoadFailure = () => { this.setState({ buildsLoading: false, loadError: true, }); }; private updateSelectedBuild = (item: any) => { const { updateCommand } = this.props; updateCommand('extraFields.buildNumber', item.number); this.setState({ selectedBuild: item.number }); }; private initialize = (command: IPipelineCommand) => { this.props.updateCommand('triggerInvalid', true); const trigger = command.trigger as IBuildTrigger; // These fields will be added to the trigger when the form is submitted this.props.updateCommand('extraFields', { buildNumber: get(command, 'extraFields.buildNumber', '') }); this.setState({ buildsLoading: true, loadError: false, }); if (trigger.buildNumber) { this.updateSelectedBuild(trigger.buildInfo); } // do not re-initialize if the trigger has changed to some other type if (trigger.type !== this.props.buildTriggerType) { return; } observableFrom(IgorService.listBuildsForJob(trigger.master, trigger.job)) .pipe(takeUntil(this.destroy$)) .subscribe(this.buildLoadSuccess, this.buildLoadFailure); }; private manuallySpecify = () => { this.setState({ explicitBuild: true, }); }; private explicitlyUpdateBuildNumber = (event: React.ChangeEvent) => { this.updateSelectedBuild({ number: event.target.value }); }; public componentDidMount() { this.initialize(this.props.command); } public componentWillUnmount(): void { this.destroy$.next(); } private handleBuildChanged = (option: Option): void => { this.updateSelectedBuild({ number: option.number }); }; private optionRenderer = (build: Option) => { return ( Build {build.number} {buildDisplayName(build as IBuildInfo)}({timestamp(build.timestamp)}) ); }; public render() { const { builds, buildsLoading, loadError, selectedBuild, explicitBuild } = this.state; const loadingBuilds = (
); const errorLoadingBuilds =
Error loading builds!
; const noBuildsFound = (

No builds found

); return (
{explicitBuild ? ( ) : buildsLoading ? ( loadingBuilds ) : loadError ? ( errorLoadingBuilds ) : builds.length <= 0 ? ( noBuildsFound ) : ( )}
{!explicitBuild && ( )}
); } }