import { UISref } from '@uirouter/react'; import { flatMap, get, memoize } from 'lodash'; import { DateTime } from 'luxon'; import React from 'react'; import { Modal } from 'react-bootstrap'; import type { Subscription } from 'rxjs'; import { DeletePipelineTemplateV2Modal } from './DeletePipelineTemplateV2Modal'; import { PipelineTemplateReader } from '../PipelineTemplateReader'; import { ShowPipelineTemplateJsonModal } from '../../actions/templateJson/ShowPipelineTemplateJsonModal'; import { CreatePipelineFromTemplate } from './createPipelineFromTemplate'; import type { IPipelineTemplateV2, IPipelineTemplateV2Collections, IPipelineTemplateV2VersionSelections, } from '../../../../domain/IPipelineTemplateV2'; import { PipelineTemplateV2Service } from './pipelineTemplateV2.service'; import { ReactSelectInput } from '../../../../presentation'; import type { IStateChange } from '../../../../reactShims'; import { ReactInjector } from '../../../../reactShims'; import './PipelineTemplatesV2.less'; const { convertTemplateVersionToId, getTemplateVersion } = PipelineTemplateV2Service; export interface IPipelineTemplatesV2State { fetchError: string; searchValue: string; viewTemplateVersion?: string; deleteTemplateVersion?: string; selectedTemplate: IPipelineTemplateV2; templateVersionSelections: IPipelineTemplateV2VersionSelections; templates: IPipelineTemplateV2Collections; } export const PipelineTemplatesV2Error = (props: { message: string }) => { return (
{props.message}
); }; export class PipelineTemplatesV2 extends React.Component<{}, IPipelineTemplatesV2State> { private routeChangedSubscription: Subscription = null; public state: IPipelineTemplatesV2State = { fetchError: null, searchValue: '', selectedTemplate: null, templates: {}, viewTemplateVersion: ReactInjector.$stateParams.templateId, templateVersionSelections: {}, }; public componentDidMount() { this.fetchTemplates(); this.routeChangedSubscription = ReactInjector.stateEvents.stateChangeSuccess.subscribe(this.onRouteChanged); } public componentWillUnmount() { this.routeChangedSubscription.unsubscribe(); } private fetchTemplates() { PipelineTemplateReader.getV2PipelineTemplateList().then( (templates) => this.setState({ templates, templateVersionSelections: {} }), (err) => { const errorString = get(err, 'data.message', get(err, 'message', '')); if (errorString) { this.setState({ fetchError: errorString }); } else { this.setState({ fetchError: 'An unknown error occurred while fetching templates' }); } }, ); } private onRouteChanged = (stateChange: IStateChange) => { const { to, toParams } = stateChange; if (to.name === 'home.pipeline-templates') { this.setState({ viewTemplateVersion: null }); } else if (to.name === 'home.pipeline-templates.pipeline-templates-detail') { const { templateId } = toParams as { templateId?: string }; this.setState({ viewTemplateVersion: templateId || null }); } }; private sortTemplates = ( templates: Array<[string, IPipelineTemplateV2[]]>, ): Array<[string, IPipelineTemplateV2[]]> => { return templates.sort(([a]: [string, IPipelineTemplateV2[]], [b]: [string, IPipelineTemplateV2[]]) => { const caseInsensitiveA = a.toLowerCase(); const caseInsensitiveB = b.toLowerCase(); if (caseInsensitiveA > caseInsensitiveB) { return 1; } else if (caseInsensitiveA < caseInsensitiveB) { return -1; } return 0; }); }; private getUpdateTimeForTemplate = (template: IPipelineTemplateV2) => { const millis = Number.parseInt(template.updateTs, 10); if (isNaN(millis)) { return ''; } const dt = DateTime.fromMillis(millis); return dt.toLocaleString(DateTime.DATETIME_SHORT); }; private dismissDetailsModal = () => { ReactInjector.$state.go('home.pipeline-templates'); }; private onSearchFieldChanged = (event: React.SyntheticEvent) => { const searchValue: string = get(event, 'target.value', ''); this.setState({ searchValue }); }; // Creates a cache key suitable for _.memoize private filterMemoizeResolver = ( templateCollections: Array<[string, IPipelineTemplateV2[]]>, query: string, ): string => { const templateIds = flatMap(templateCollections, ([, templateCollection]) => templateCollection.map((template) => getTemplateVersion(template)), ); return `${templateIds.join('')}${query}`; }; private filterSearchResults = memoize( ( templateCollections: Array<[string, IPipelineTemplateV2[]]>, query: string, ): Array<[string, IPipelineTemplateV2[]]> => { const searchValue = query.trim().toLowerCase(); if (!searchValue) { return templateCollections; } else { return templateCollections.filter(([, templateCollection]) => templateCollection.some( ({ metadata: { name = '', description = '', owner = '' } = {} }) => name.toLowerCase().includes(searchValue) || description.toLowerCase().includes(searchValue) || owner.toLowerCase().includes(searchValue), ), ); } }, this.filterMemoizeResolver, ); private getViewTemplate = () => this.findTemplate('viewTemplateVersion'); private getDeleteTemplate = () => this.findTemplate('deleteTemplateVersion'); private findTemplate(actionKey: 'viewTemplateVersion' | 'deleteTemplateVersion') { const { [actionKey]: templateVersion, templates } = this.state; const templateId = convertTemplateVersionToId(templateVersion); const { [templateId]: templateCollection = [] } = templates; return templateCollection.find((template) => getTemplateVersion(template) === templateVersion); } private handleCreatePipelineClick(template: IPipelineTemplateV2): void { this.setState({ selectedTemplate: template }); } public handleCreatePipelineModalClose = () => { this.setState({ selectedTemplate: null }); }; private handleSelectedTemplateVersionChange = (e: React.ChangeEvent, templateId: string) => this.setState({ templateVersionSelections: { ...this.state.templateVersionSelections, [templateId]: e.target.value }, }); public render() { const { templates, selectedTemplate, searchValue, fetchError, viewTemplateVersion, deleteTemplateVersion, templateVersionSelections, } = this.state; const detailsTemplate = viewTemplateVersion ? this.getViewTemplate() : null; const searchPerformed = searchValue.trim() !== ''; const filteredResults = this.sortTemplates(this.filterSearchResults(Object.entries(templates), searchValue)); const resultsAvailable = filteredResults.length > 0; const deleteTemplate = deleteTemplateVersion ? this.getDeleteTemplate() : null; return ( <>

Pipeline Templates input && input.focus()} onChange={this.onSearchFieldChanged} value={searchValue} />

{fetchError && ( )} {searchPerformed && !resultsAvailable && (

No matches found for '{searchValue}'

)} {resultsAvailable && ( {filteredResults.map(([templateId, templateCollection]) => { const templateVersion = templateVersionSelections[templateId] || getTemplateVersion(templateCollection[0]); const currentTemplate = templateCollection.find( (template) => getTemplateVersion(template) === templateVersion, ); const { metadata } = currentTemplate; return ( ); })}
Name Owner Updated Version Actions
{metadata.name || '-'} {metadata.owner || '-'} {this.getUpdateTimeForTemplate(currentTemplate) || '-'} this.handleSelectedTemplateVersionChange(e, templateId)} value={templateVersion} stringOptions={templateCollection.map((templateOption) => getTemplateVersion(templateOption), )} />
)}
{detailsTemplate && ( {}}> )} {deleteTemplate && ( { this.fetchTemplates(); this.setState({ deleteTemplateVersion: null }); }} /> )} {selectedTemplate && ( )} ); } }