import { AccordionButton, AccordionItem, AccordionPanel, Alert, Box, Flex, HStack, Tooltip, chakra, useAccordionContext, useToast, } from '@chakra-ui/react'; import { motion } from 'framer-motion'; import startCase from 'lodash/startCase'; import { forwardRef, memo, useEffect, useMemo, useState } from 'react'; import isEqual from 'react-fast-compare'; import { Else, If, Then } from 'react-if'; import { BGPTable, Path, TextOutput } from '~/components'; import { useConfig } from '~/context'; import { Countdown, DynamicIcon } from '~/elements'; import { useColorValue, useDevice, useFormState, useLGQuery, useMobile, useStrf, useTableToString, } from '~/hooks'; import { isStringOutput, isStructuredOutput } from '~/types'; import { CopyButton } from './copy-button'; import { FormattedError } from './formatted-error'; import { isFetchError, isLGError, isLGOutputOrError, isStackError } from './guards'; import { ResultHeader } from './header'; import { RequeryButton } from './requery-button'; import type { ErrorLevels } from '~/types'; interface ResultProps { index: number; queryLocation: string; } const AnimatedAccordionItem = motion(AccordionItem); const AccordionHeaderWrapper = chakra('div', { baseStyle: { display: 'flex', justifyContent: 'space-between', _hover: { bg: 'blackAlpha.50' }, _focus: { boxShadow: 'outline' }, }, }); const _Result: React.ForwardRefRenderFunction = ( props: ResultProps, ref, ) => { const { index, queryLocation } = props; const toast = useToast(); const { web, cache, messages } = useConfig(); const { index: indices, setIndex } = useAccordionContext(); const getDevice = useDevice(); const device = getDevice(queryLocation); const isMobile = useMobile(); const color = useColorValue('black', 'white'); const scrollbar = useColorValue('blackAlpha.300', 'whiteAlpha.300'); const scrollbarHover = useColorValue('blackAlpha.400', 'whiteAlpha.400'); const scrollbarBg = useColorValue('blackAlpha.50', 'whiteAlpha.50'); const addResponse = useFormState(s => s.addResponse); const form = useFormState(s => s.form); const [errorLevel, _setErrorLevel] = useState('error'); const setErrorLevel = (level: ResponseLevel): void => { let e: ErrorLevels = 'error'; switch (level) { case 'success': e = level; break; case 'warning' || 'error': e = 'warning'; break; } _setErrorLevel(e); }; const { data, error, isLoading, refetch, isFetchedAfterMount } = useLGQuery( { queryLocation, queryTarget: form.queryTarget, queryType: form.queryType }, { onSuccess(data) { if (device !== null) { addResponse(device.id, data); } if (isLGOutputOrError(data)) { console.error(data); setErrorLevel(data.level); } }, onError(error) { console.error({ error }); if (isLGOutputOrError(error)) { setErrorLevel(error.level); } }, }, ); const isError = useMemo(() => isLGOutputOrError(data), [data, error]); const isCached = useMemo(() => data?.cached || !isFetchedAfterMount, [data, isFetchedAfterMount]); const strF = useStrf(); const cacheLabel = strF(web.text.cacheIcon, { time: data?.timestamp }); const errorKeywords = useMemo(() => { let kw = [] as string[]; if (isLGError(data)) { kw = data.keywords; } return kw; }, [data]); // Parse the the response and/or the error to determine from where to extract the error message. const errorMsg = useMemo(() => { if (isLGError(error)) { return error.output as string; } if (isLGOutputOrError(data)) { return data.output as string; } if (isFetchError(error)) { return startCase(error.statusText); } if (isStackError(error) && error.message.toLowerCase().startsWith('timeout')) { return messages.requestTimeout; } if (isStackError(error)) { return startCase(error.message); } return messages.general; }, [error, data, messages.general, messages.requestTimeout]); const tableComponent = useMemo(() => { let result = false; if (data?.format === 'application/json') { result = true; } return result; }, [data?.format]); let copyValue = data?.output as string; const formatData = useTableToString(form.queryTarget, data, [data?.format]); if (data?.format === 'application/json') { copyValue = formatData(); } if (error) { copyValue = errorMsg; } // Signal to the group that this result is done loading. useEffect(() => { // Only set the index if it's not already set and the query is finished loading. if (Array.isArray(indices) && indices.length === 0 && !isLoading) { // Only set the index if the response has data or an error. if (data || isError) { setIndex([index]); } } }, [data, index, indices, isLoading, isError, setIndex]); if (device === null) { const id = `toast-queryLocation-${index}-${queryLocation}`; if (!toast.isActive(id)) { toast({ id, title: messages.general, description: `Configuration for device with ID '${queryLocation}' not found.`, status: 'error', isClosable: true, }); } return <>; } return ( {isStructuredOutput(data) && data.level === 'success' && tableComponent && ( )} {isStructuredOutput(data) && data.level === 'success' && tableComponent ? ( {data.output} ) : isStringOutput(data) && data.level === 'success' && !tableComponent ? ( {data.output} ) : isStringOutput(data) && data.level !== 'success' ? ( ) : ( )} ); }; export const Result = memo(forwardRef(_Result), isEqual);