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 (
<>
{fetchError && (
)}
{searchPerformed && !resultsAvailable && (
No matches found for '{searchValue}'
)}
{resultsAvailable && (
| Name |
Owner |
Updated |
Version
|
Actions |
{filteredResults.map(([templateId, templateCollection]) => {
const templateVersion =
templateVersionSelections[templateId] || getTemplateVersion(templateCollection[0]);
const currentTemplate = templateCollection.find(
(template) => getTemplateVersion(template) === templateVersion,
);
const { metadata } = currentTemplate;
return (
| {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 && (
)}
>
);
}
}