import { Heading, Box, Icon, Span, Stack, Text, Timeline, Tabs} from '@chakra-ui/react'; import React, { Fragment, useMemo } from "react"; import { BsExclamationTriangle, BsSkipForward, BsStoplights } from "react-icons/bs"; import { MdMusicNote } from "react-icons/md"; import type {ErrorLike, JsonPlayObject, LifecycleStep} from "../../core/Atomic"; import { patchObject } from "../../core/DataUtils"; import { isErrorIsh } from "../../core/ErrorUtils"; import { timelineIconProps, timelineTextFormatting } from "../utils/ComponentUtils"; import { ChakraCodeBlockShort } from "./CodeBlock"; import { ErrorAlert } from "./ErrorAlert"; import { MSCollapsible, type MSCollapsibleExternalProps } from "./MSCollapsible"; import { PlayData } from "./play/PlayData"; import { Muted } from "./Typography"; import { JsonDiffPatch } from "./diffs/JsonDiff"; import type { Changeset } from 'json-diff-ts'; export interface LifeycleStepsTimelineProps extends MSCollapsibleExternalProps { steps: LifecycleStep[] original: JsonPlayObject } export interface DiffElementData { input?: object original: T patch?: Changeset patchFailed?: boolean error?: ErrorLike indexKey?: string | number } export interface DiffElementResult { input?: React.JSX.Element, diff?: React.JSX.Element, patch?: React.JSX.Element, identical?: boolean, final?: T error?: React.JSX.Element indexKey?: string} export const diffElement = (data: DiffElementData): DiffElementResult => { const { input, original, patch, patchFailed: patchFailedInitial, error, indexKey = '' } = data; const inputCode = input !== undefined ? : undefined; const patchCode = patch !== undefined ? : undefined; const baseResult: Partial> = { input: inputCode, patch: patchCode } if (patch === undefined) { if (error !== undefined) { return { ...baseResult } } return { ...baseResult, final: original, identical: true, patch: Data was identical after patching. } } if (patchFailedInitial) { return { ...baseResult, patch: undefined, final: original } } try { const current = structuredClone(original); const right: T = patchObject(structuredClone(current), patch); const diff = ; return { ...baseResult, diff, final: right }; } catch (e) { const error = {inputCode}{patchCode}} />; return { ...baseResult, final: original, error }; } } export const diffElements = (original: JsonPlayObject, steps: LifecycleStep[]): [(React.JSX.Element | null)[], JsonPlayObject?] => { const currentPlay: JsonPlayObject = structuredClone(original); // JSON.parse(JSON.stringify(original)); let patchFailed = false; const diffElements: (React.JSX.Element | null)[] = []; let index = 0; for (const step of steps) { index++; const { patch, error } = step; const diffElementResults = diffElement({original: structuredClone(currentPlay.data), patch, patchFailed, error, indexKey: index}); const { patch: patchElm, diff, error: errorElm, final, } = diffElementResults if(errorElm !== undefined) { patchFailed = true; diffElements.push(errorElm); } else if(diff !== undefined) { diffElements.push(diff); if(final !== undefined) { currentPlay.data = final; } } else if (patchElm !== undefined) { diffElements.push(patchElm); } else { diffElements.push(null); } } return [diffElements, !patchFailed ? currentPlay : undefined] } export const TransformSteps = (props: LifeycleStepsTimelineProps) => { const { steps, original, collapsibleOpen } = props; const [diffs, finalPlay] = useMemo(() => diffElements(original, steps), [steps, original]); return ( {steps.map((x, index) => { const { patch, inputs, source, error, flowKnownState, flowReason, flowResult, stageName, stageType, hook } = x; let timelineIcon: React.JSX.Element, iconProps: Record = {}, summary: React.JSX.Element, alertStatus: "error" | "info" | "warning" | "success" | "neutral" | undefined = undefined; if(error === undefined && error !== null) { timelineIcon = ; iconProps = flowResult === 'continue' ? {color: "green.focusRing"} : {color: "red.focusRing"}; summary = was completed{flowResult === 'stop' ? and stopped due to onSuccess condition : null}; if(patch === undefined) { summary = {summary} with no change to Play; } } else { if(flowKnownState === 'skip') { timelineIcon = ; summary = was skipped.; alertStatus = 'info'; } else if(flowKnownState === 'prereq') { timelineIcon = ; iconProps = {color: "orange.focusRing"}; summary = was not completed due to prerequisite failure.; alertStatus = "warning"; } else { if(flowResult === 'continue') { timelineIcon = ; iconProps = {color: "orange.focusRing"}; summary = encountered an error but will continue due to onFailure condition.; } else { timelineIcon = ; iconProps = {color: "red.focusRing"}; summary = encountered an error. } } } let inputsElm: React.JSX.Element | undefined = undefined; if(inputs !== undefined && inputs.length > 0) { inputsElm = ( {inputs.map((y, inputsIndex) => )} ); } let diffElm: React.JSX.Element | undefined = undefined; if(diffs[index] !== null) { diffElm = {diffs[index]} } return {timelineIcon} Stage {stageType}-{stageName} in Hook {hook} from {source} {summary}} defaultOpen={collapsibleOpen} disableUntil="md" unmountOnExit timeline> {error !== undefined && error !== null ? : null} Inputs Diff {inputsElm ?? No Inputs recorded} {diffElm ?? No diff recorded or Play was identical after transform} })} {finalPlay !== undefined ? ( Final Play after all Transforms ) : null} ) }