diff --git a/public/pages/workflows/import_workflow/import_workflow_modal.tsx b/public/pages/workflows/import_workflow/import_workflow_modal.tsx index e913b058..8fcef547 100644 --- a/public/pages/workflows/import_workflow/import_workflow_modal.tsx +++ b/public/pages/workflows/import_workflow/import_workflow_modal.tsx @@ -27,6 +27,7 @@ import { getObjFromJsonOrYamlString, isValidUiWorkflow, isValidWorkflow, + isCompatibleWorkflow, } from '../../../utils'; import { getCore } from '../../../services'; import { @@ -43,7 +44,7 @@ import { WORKFLOW_NAME_RESTRICTIONS, } from '../../../../common'; import { WORKFLOWS_TAB } from '../workflows'; -import { getDataSourceId } from '../../../utils/utils'; +import { getDataSourceId, getEffectiveVersion, formatDisplayVersion } from '../../../utils/utils'; interface ImportWorkflowModalProps { isImportModalOpen: boolean; @@ -61,6 +62,17 @@ interface ImportWorkflowModalProps { export function ImportWorkflowModal(props: ImportWorkflowModalProps) { const dispatch = useAppDispatch(); const dataSourceId = getDataSourceId(); + const [dataSourceVersion, setDataSourceVersion] = useState< + string | undefined + >(undefined); + useEffect(() => { + async function getVersion() { + if (dataSourceId !== undefined) { + setDataSourceVersion(await getEffectiveVersion(dataSourceId)); + } + } + getVersion(); + }, [dataSourceId]); const { workflows } = useSelector((state: AppState) => state.workflows); // workflow name state @@ -86,6 +98,9 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) { return description.length > MAX_DESCRIPTION_LENGTH; } + // State for tracking workflow template compatibility with current data source version + const [isCompatible, setIsCompatible] = useState(true); + // transient importing state for button state const [isImporting, setIsImporting] = useState(false); @@ -115,6 +130,16 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) { } }, [fileObj]); + useEffect(() => { + async function checkCompatibility() { + if (isValidWorkflow(fileObj)) { + const isCompatible = await isCompatibleWorkflow(fileObj, dataSourceId); + setIsCompatible(isCompatible); + } + } + checkCompatibility(); + }, [fileObj, dataSourceId]); + function onModalClose(): void { props.setIsImportModalOpen(false); setFileContents(undefined); @@ -142,6 +167,18 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) { )} + {isValidWorkflow(fileObj) && !isCompatible && dataSourceVersion && ( + <> + + + + + + )} {isValidWorkflow(fileObj) && !isValidUiWorkflow(fileObj) && ( <> @@ -230,6 +267,7 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) { { try { const url = dataSourceId diff --git a/public/utils/utils.tsx b/public/utils/utils.tsx index 26163c58..f59d22fb 100644 --- a/public/utils/utils.tsx +++ b/public/utils/utils.tsx @@ -157,6 +157,29 @@ export function isValidWorkflow(workflowObj: any): boolean { return workflowObj?.name !== undefined; } +// Determines if a file used for import workflow is compatible with the current data source version. +export async function isCompatibleWorkflow( + workflowObj: any, + dataSourceId?: string | undefined +): Promise { + const compatibility = workflowObj?.version?.compatibility; + + // Default to true when compatibility cannot be assessed (empty/invalid compatibility array or MDS disabled.) + if (!Array.isArray(compatibility) || compatibility.length === 0 || dataSourceId === undefined) { + return true; + } + + const dataSourceVersion = await getEffectiveVersion(dataSourceId); + const [effectiveMajorVersion, effectiveMinorVersion] = dataSourceVersion.split('.').map(Number); + + // Checks if any version in compatibility array matches the current dataSourceVersion (major.minor) + return compatibility.some(compatibleVersion => { + const [compatibleMajor, compatibleMinor] = compatibleVersion.split('.').map(Number); + return effectiveMajorVersion === compatibleMajor && effectiveMinorVersion === compatibleMinor; + }); +} + + export function isValidUiWorkflow(workflowObj: any): boolean { return ( isValidWorkflow(workflowObj) && @@ -530,7 +553,7 @@ export const getDataSourceId = () => { export const isDataSourceReady = (dataSourceId?: string) => { const dataSourceEnabled = getDataSourceEnabled().enabled; - return !dataSourceEnabled || (dataSourceId && dataSourceId !== ''); + return !dataSourceEnabled || dataSourceId !== undefined; }; // converts camelCase to a space-delimited string with the first word capitalized. @@ -912,3 +935,15 @@ export const getEffectiveVersion = async ( return MIN_SUPPORTED_VERSION; } }; + + +/** + * Formats version string to show only major.minor numbers + * Example: "3.0.0-alpha1" -> "3.0" + */ +export function formatDisplayVersion(version: string): string { + // Take first two parts of version number (major.minor) + const [major, minor] = version.split('.'); + return `${major}.${minor}`; +} +