diff --git a/web/src/components/ResizablePanels/FillPanel.tsx b/web/src/components/ResizablePanels/FillPanel.tsx new file mode 100644 index 0000000000..4973a4df75 --- /dev/null +++ b/web/src/components/ResizablePanels/FillPanel.tsx @@ -0,0 +1,5 @@ +import * as Spaces from 'react-spaces'; + +const FillPanel: React.FC = ({children}) => {children}; + +export default FillPanel; diff --git a/web/src/components/ResizablePanels/LeftPanel.tsx b/web/src/components/ResizablePanels/LeftPanel.tsx new file mode 100644 index 0000000000..011a29a559 --- /dev/null +++ b/web/src/components/ResizablePanels/LeftPanel.tsx @@ -0,0 +1,29 @@ +import * as Spaces from 'react-spaces'; +import useResizablePanel, {TPanel, TSize} from '../ResizablePanels/hooks/useResizablePanel'; +import Splitter from '../ResizablePanels/Splitter'; + +interface IProps { + panel: TPanel; + order?: number; + children(size: TSize): React.ReactNode; +} + +const LeftPanel = ({panel, order = 1, children}: IProps) => { + const {size, toggle, onStopResize} = useResizablePanel({panel}); + + return ( + onStopResize(newSize)} + minimumSize={size.minSize} + maximumSize={size.maxSize} + size={size.size} + key={size.name} + order={order} + handleRender={props => toggle()} />} + > + {children(size)} + + ); +}; + +export default LeftPanel; diff --git a/web/src/components/ResizablePanels/ResizablePanels.styled.ts b/web/src/components/ResizablePanels/ResizablePanels.styled.ts index 5efd291885..2cc292409b 100644 --- a/web/src/components/ResizablePanels/ResizablePanels.styled.ts +++ b/web/src/components/ResizablePanels/ResizablePanels.styled.ts @@ -1,4 +1,4 @@ -import styled, {createGlobalStyle} from 'styled-components'; +import styled, {createGlobalStyle, css} from 'styled-components'; export const GlobalStyle = createGlobalStyle` .spaces-resize-handle { @@ -11,6 +11,30 @@ export const ButtonContainer = styled.div` position: absolute; right: -12px; top: 48px; + z-index: 100; `; export const SplitterContainer = styled.div``; + +export const PanelContainer = styled.div<{$isOpen: boolean}>` + background-color: ${({theme}) => theme.color.white}; + box-shadow: 0 20px 24px rgba(153, 155, 168, 0.18); + height: 100%; + overflow: visible; + overflow-y: scroll; + position: relative; + + > div { + opacity: 0; + pointer-events: none; + } + + ${({$isOpen}) => + $isOpen && + css` + > div { + opacity: 1; + pointer-events: auto; + } + `} +`; diff --git a/web/src/components/ResizablePanels/ResizablePanels.tsx b/web/src/components/ResizablePanels/ResizablePanels.tsx index 6b8c432c1f..1c2ee7671e 100644 --- a/web/src/components/ResizablePanels/ResizablePanels.tsx +++ b/web/src/components/ResizablePanels/ResizablePanels.tsx @@ -1,84 +1,12 @@ import * as Spaces from 'react-spaces'; -import {useCallback, useMemo} from 'react'; import * as S from './ResizablePanels.styled'; -import useResizablePanels, {TSize} from './hooks/useResizablePanels'; -import Splitter from './Splitter'; - -export type TPanelComponentProps = {size: TSize}; - -export type TPanel = { - name: string; - isDefaultOpen?: boolean; - minSize?: number; - maxSize?: number; - position?: 'left' | 'right' | undefined; - component(props: TPanelComponentProps): React.ReactElement; -}; - -interface IProps { - panels: TPanel[]; -} - -const ResizablePanels = ({panels}: IProps) => { - const {onStopResize, toggle, sizes} = useResizablePanels({panels}); - - const getPanel = useCallback((name: string) => panels.find(panel => panel.name === name), [panels]); - - const elements = useMemo( - () => - Object.values(sizes).reduce((acc, size, index) => { - const {component: Component, position} = getPanel(size.name)!; - - if (position === 'left') { - return acc.concat([ - onStopResize(size, newSize)} - minimumSize={size.minSize} - maximumSize={size.maxSize} - size={size.size} - key={size.name} - handleRender={props => ( - toggle(size)} /> - )} - order={index + 1} - > - - , - ]); - } - - if (position === 'right') { - return acc.concat([ - onStopResize(size, newSize)} - minimumSize={size.minSize} - maximumSize={size.maxSize} - size={size.size} - key={size.name} - handleRender={props => ( - toggle(size)} /> - )} - order={index + 1} - > - - , - ]); - } - - return acc.concat( - - - - ); - }, []), - [getPanel, onStopResize, sizes, toggle] - ); +const ResizablePanels: React.FC = ({children}) => { return ( <> - {elements} + {children} ); diff --git a/web/src/components/ResizablePanels/RightPanel.tsx b/web/src/components/ResizablePanels/RightPanel.tsx new file mode 100644 index 0000000000..ff0e972e11 --- /dev/null +++ b/web/src/components/ResizablePanels/RightPanel.tsx @@ -0,0 +1,29 @@ +import * as Spaces from 'react-spaces'; +import Splitter from '../ResizablePanels/Splitter'; +import useResizablePanel, {TPanel, TSize} from '../ResizablePanels/hooks/useResizablePanel'; + +interface IProps { + panel: TPanel; + order?: number; + children(size: TSize): React.ReactNode; +} + +const RightPanel = ({panel, order = 1, children}: IProps) => { + const {size, toggle, onStopResize} = useResizablePanel({panel}); + + return ( + onStopResize(newSize)} + minimumSize={size.minSize} + maximumSize={size.maxSize} + size={size.size} + key={size.name} + order={order} + handleRender={props => toggle()} />} + > + {children(size)} + + ); +}; + +export default RightPanel; diff --git a/web/src/components/ResizablePanels/hooks/useResizablePanel.ts b/web/src/components/ResizablePanels/hooks/useResizablePanel.ts new file mode 100644 index 0000000000..8e534b997f --- /dev/null +++ b/web/src/components/ResizablePanels/hooks/useResizablePanel.ts @@ -0,0 +1,63 @@ +import {useCallback, useState} from 'react'; + +export type TPanel = { + name: string; + isDefaultOpen?: boolean; + minSize?: number; + maxSize?: number; +}; + +interface IProps { + panel: TPanel; +} + +export type TSize = Exclude & { + size: number; + isOpen: boolean; + minSize: number; + maxSize: number; +}; + +const useResizablePanel = ({panel}: IProps) => { + const [size, setSize] = useState({ + ...panel, + size: (panel.isDefaultOpen && panel.maxSize) || panel.minSize || 0, + isOpen: panel.isDefaultOpen || false, + minSize: panel.minSize || 0, + maxSize: panel.maxSize || 0, + }); + + const toggle = useCallback(() => { + if (size.size <= size.minSize) { + const currentSize = size.maxSize; + setSize({ + ...size, + size: currentSize, + isOpen: currentSize > size.minSize, + }); + return; + } + setSize({ + ...size, + size: size.minSize || 0, + isOpen: false, + }); + }, [size]); + + const onStopResize = useCallback( + newSize => { + const currentSize = Number(newSize); + + setSize({ + ...size, + size: currentSize, + isOpen: currentSize > size.minSize, + }); + }, + [size] + ); + + return {toggle, onStopResize, size}; +}; + +export default useResizablePanel; diff --git a/web/src/components/ResizablePanels/hooks/useResizablePanels.ts b/web/src/components/ResizablePanels/hooks/useResizablePanels.ts deleted file mode 100644 index 2b8812869e..0000000000 --- a/web/src/components/ResizablePanels/hooks/useResizablePanels.ts +++ /dev/null @@ -1,76 +0,0 @@ -import {useCallback, useState} from 'react'; -import {TPanel} from '../ResizablePanels'; - -interface IProps { - panels: TPanel[]; -} - -export type TSize = Exclude & { - size: number; - isOpen: boolean; - minSize: number; - maxSize: number; -}; - -type TSizeState = Record; - -const getInitialState = (panels: TPanel[]): TSizeState => - panels.reduce( - (acc, {component, ...panel}) => ({ - ...acc, - [panel.name]: { - ...panel, - size: panel.isDefaultOpen ? panel.maxSize : panel.minSize, - isOpen: panel.isDefaultOpen, - }, - }), - {} - ); - -const useResizablePanels = ({panels}: IProps) => { - const [sizes, setSizes] = useState(getInitialState(panels)); - - const setSize = useCallback((size: TSize) => { - setSizes(prev => ({ - ...prev, - [size.name]: size, - })); - }, []); - - const toggle = useCallback( - (size: TSize) => { - if (size.size <= size.minSize) { - const currentSize = size.maxSize; - setSize({ - ...size, - size: currentSize, - isOpen: currentSize > size.minSize, - }); - return; - } - setSize({ - ...size, - size: size.minSize || 0, - isOpen: false, - }); - }, - [setSize] - ); - - const onStopResize = useCallback( - (size: TSize, newSize) => { - const currentSize = Number(newSize); - - setSize({ - ...size, - size: currentSize, - isOpen: currentSize > size.minSize, - }); - }, - [setSize] - ); - - return {toggle, onStopResize, sizes}; -}; - -export default useResizablePanels; diff --git a/web/src/components/ResizablePanels/index.ts b/web/src/components/ResizablePanels/index.ts index 367995e006..24bf3e76df 100644 --- a/web/src/components/ResizablePanels/index.ts +++ b/web/src/components/ResizablePanels/index.ts @@ -1,2 +1,8 @@ +import LeftPanel from './LeftPanel'; +import RightPanel from './RightPanel'; +import FillPanel from './FillPanel'; +import {PanelContainer} from './ResizablePanels.styled'; + // eslint-disable-next-line no-restricted-exports export {default} from './ResizablePanels'; +export {LeftPanel, RightPanel, FillPanel, PanelContainer}; diff --git a/web/src/components/RunDetailTest/RunDetailTest.tsx b/web/src/components/RunDetailTest/RunDetailTest.tsx index 89d05047ba..567023303c 100644 --- a/web/src/components/RunDetailTest/RunDetailTest.tsx +++ b/web/src/components/RunDetailTest/RunDetailTest.tsx @@ -1,11 +1,10 @@ -import PanelLayout from 'components/ResizablePanels'; -import {useMemo} from 'react'; import TestRun from 'models/TestRun.model'; import TestRunEvent from 'models/TestRunEvent.model'; import * as S from './RunDetailTest.styled'; -import {getTestPanel} from './TestPanel'; import SetupAlert from '../SetupAlert/SetupAlert'; -import {getSpanDetailsPanel} from './SpanDetailPanel'; +import ResizablePanels from '../ResizablePanels/ResizablePanels'; +import SpanDetailsPanel from './SpanDetailsPanel'; +import TestPanel from './TestPanel'; interface IProps { run: TestRun; @@ -14,11 +13,13 @@ interface IProps { } const RunDetailTest = ({run, runEvents, testId}: IProps) => { - const panels = useMemo(() => [getSpanDetailsPanel(), getTestPanel(testId, run, runEvents)], [run, runEvents, testId]); return ( - + + + + ); }; diff --git a/web/src/components/RunDetailTest/SpanDetailPanel.tsx b/web/src/components/RunDetailTest/SpanDetailPanel.tsx deleted file mode 100644 index 68ddc49c7c..0000000000 --- a/web/src/components/RunDetailTest/SpanDetailPanel.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import {useSpan} from 'providers/Span/Span.provider'; -import {TPanel, TPanelComponentProps} from '../ResizablePanels/ResizablePanels'; -import SpanDetail from '../SpanDetail/SpanDetail'; -import * as S from '../RunDetailTrace/RunDetailTrace.styled'; - -const SpanDetailsPanel = ({size: {isOpen}}: TPanelComponentProps) => { - const {selectedSpan} = useSpan(); - - return ( - - - - ); -}; - -export const getSpanDetailsPanel = (): TPanel => ({ - name: 'SPAN_DETAILS', - minSize: 15, - maxSize: 320, - position: 'left', - component: props => , -}); - -export default SpanDetailsPanel; diff --git a/web/src/components/RunDetailTest/SpanDetailsPanel.tsx b/web/src/components/RunDetailTest/SpanDetailsPanel.tsx new file mode 100644 index 0000000000..fa89c67e81 --- /dev/null +++ b/web/src/components/RunDetailTest/SpanDetailsPanel.tsx @@ -0,0 +1,25 @@ +import {useSpan} from 'providers/Span/Span.provider'; +import SpanDetail from '../SpanDetail/SpanDetail'; +import {LeftPanel, PanelContainer} from '../ResizablePanels'; + +const panel = { + name: 'SPAN_DETAILS', + minSize: 15, + maxSize: 320, +}; + +const SpanDetailsPanel = () => { + const {selectedSpan} = useSpan(); + + return ( + + {size => ( + + + + )} + + ); +}; + +export default SpanDetailsPanel; diff --git a/web/src/components/RunDetailTest/TestPanel.tsx b/web/src/components/RunDetailTest/TestPanel.tsx index 8119bce150..0e31313365 100644 --- a/web/src/components/RunDetailTest/TestPanel.tsx +++ b/web/src/components/RunDetailTest/TestPanel.tsx @@ -23,7 +23,7 @@ import TestRunAnalytics from 'services/Analytics/TestRunAnalytics.service'; import AssertionService from 'services/Assertion.service'; import * as S from './RunDetailTest.styled'; import Visualization from './Visualization'; -import {TPanel} from '../ResizablePanels/ResizablePanels'; +import {FillPanel} from '../ResizablePanels'; const TABS = { SPECS: 'specs', @@ -109,130 +109,125 @@ const TestPanel = ({run, testId, runEvents}: IProps) => { ); return ( - - - - {run.state === TestState.FINISHED && ( - { - TestRunAnalytics.onSwitchDiagramView(type); - setVisualizationType(type); + + + + + {run.state === TestState.FINISHED && ( + { + TestRunAnalytics.onSwitchDiagramView(type); + setVisualizationType(type); + }} + type={visualizationType} + /> + )} + + + + + + + {isTestSpecFormOpen && ( + { + setSelectorSuggestions([]); + setPrevSelector(''); + onSubmit(values); + }} + isValid={isValid} + onIsValid={onIsValid} + runId={run.id} + testId={testId} + {...formProps} + onCancel={() => { + setSelectorSuggestions([]); + setPrevSelector(''); + close(); + }} + onClearSelectorSuggestions={() => { + setSelectorSuggestions([]); + }} + onClickPrevSelector={prevSelector => { + setPrevSelector(prevSelector); }} - type={visualizationType} /> )} - - - - - - - {isTestSpecFormOpen && ( - { - setSelectorSuggestions([]); - setPrevSelector(''); - onSubmit(values); - }} - isValid={isValid} - onIsValid={onIsValid} - runId={run.id} - testId={testId} - {...formProps} - onCancel={() => { - setSelectorSuggestions([]); - setPrevSelector(''); - close(); - }} - onClearSelectorSuggestions={() => { - setSelectorSuggestions([]); - }} - onClickPrevSelector={prevSelector => { - setPrevSelector(prevSelector); - }} - /> - )} - - {isTestOutputFormOpen && ( - - )} - - {!isTestSpecFormOpen && !isTestOutputFormOpen && ( - - - updateQuery( - selectedSpan - ? [ - ['selectedSpan', selectedSpan.id], - ['tab', tab], - ] - : [['tab', tab]] - ) - } - size="small" - > - - Test Specs - - } - > - - - - Test Outputs - + + {isTestOutputFormOpen && ( + + )} + + {!isTestSpecFormOpen && !isTestOutputFormOpen && ( + + + updateQuery( + selectedSpan + ? [ + ['selectedSpan', selectedSpan.id], + ['tab', tab], + ] + : [['tab', tab]] + ) } + size="small" > - - - - - - - )} - - + + Test Specs + + } + > + + + + Test Outputs + + } + > + + + + + + + )} + + + ); }; -export const getTestPanel = (testId: string, run: TestRun, runEvents: TestRunEvent[]): TPanel => ({ - name: 'TEST', - component: () => { - return ; - }, -}); - export default TestPanel; diff --git a/web/src/components/RunDetailTrace/AnalyzerPanel.tsx b/web/src/components/RunDetailTrace/AnalyzerPanel.tsx index dd633a56a3..39c5284f56 100644 --- a/web/src/components/RunDetailTrace/AnalyzerPanel.tsx +++ b/web/src/components/RunDetailTrace/AnalyzerPanel.tsx @@ -1,31 +1,34 @@ import TestRun, {isRunStateFinished} from 'models/TestRun.model'; import Trace from 'models/Trace.model'; -import {TPanel, TPanelComponentProps} from '../ResizablePanels/ResizablePanels'; import AnalyzerResult from '../AnalyzerResult/AnalyzerResult'; import SkeletonResponse from '../RunDetailTriggerResponse/SkeletonResponse'; -import * as S from './RunDetailTrace.styled'; +import {RightPanel, PanelContainer} from '../ResizablePanels'; -type TProps = TPanelComponentProps & { +interface IProps { run: TestRun; +} + +const panel = { + name: 'ANALYZER', + maxSize: 650, + minSize: 25, + isDefaultOpen: true, }; -const AnalyzerPanel = ({run, size: {isOpen}}: TProps) => { - return isRunStateFinished(run.state) ? ( - - - - ) : ( - +const AnalyzerPanel = ({run}: IProps) => { + return ( + + {size => ( + + {isRunStateFinished(run.state) ? ( + + ) : ( + + )} + + )} + ); }; -export const getAnalyzerPanel = (run: TestRun): TPanel => ({ - name: 'ANALYZER', - maxSize: 720, - minSize: 15, - isDefaultOpen: true, - position: 'right', - component: ({size}) => , -}); - export default AnalyzerPanel; diff --git a/web/src/components/RunDetailTrace/RunDetailTrace.styled.ts b/web/src/components/RunDetailTrace/RunDetailTrace.styled.ts index dc441442e1..8d4e42c35b 100644 --- a/web/src/components/RunDetailTrace/RunDetailTrace.styled.ts +++ b/web/src/components/RunDetailTrace/RunDetailTrace.styled.ts @@ -1,6 +1,6 @@ import {CloseCircleFilled} from '@ant-design/icons'; import {Typography} from 'antd'; -import styled, {css} from 'styled-components'; +import styled from 'styled-components'; export const Container = styled.div` display: flex; @@ -63,25 +63,3 @@ export const NoMatchesContainer = styled.div` export const NoMatchesText = styled(Typography.Text)` color: ${({theme}) => theme.color.textSecondary}; `; - -export const PanelContainer = styled.div<{$isOpen: boolean}>` - background-color: ${({theme}) => theme.color.white}; - box-shadow: 0 20px 24px rgba(153, 155, 168, 0.18); - height: 100%; - overflow: visible; - position: relative; - - > div { - opacity: 0; - pointer-events: none; - } - - ${({$isOpen}) => - $isOpen && - css` - > div { - opacity: 1; - pointer-events: auto; - } - `} -`; diff --git a/web/src/components/RunDetailTrace/RunDetailTrace.tsx b/web/src/components/RunDetailTrace/RunDetailTrace.tsx index 78be88c622..16f7613966 100644 --- a/web/src/components/RunDetailTrace/RunDetailTrace.tsx +++ b/web/src/components/RunDetailTrace/RunDetailTrace.tsx @@ -1,12 +1,11 @@ -import {useMemo} from 'react'; import ResizablePanels from 'components/ResizablePanels'; import TestRun from 'models/TestRun.model'; import TestRunEvent from 'models/TestRunEvent.model'; import * as S from './RunDetailTrace.styled'; import SetupAlert from '../SetupAlert'; -import {getAnalyzerPanel} from './AnalyzerPanel'; -import {getSpanDetailsPanel} from './SpanDetailsPanel'; -import {geTracePanel} from './TracePanel'; +import AnalyzerPanel from './AnalyzerPanel'; +import SpanDetailsPanel from './SpanDetailsPanel'; +import TracePanel from './TracePanel'; interface IProps { run: TestRun; @@ -20,19 +19,14 @@ export enum VisualizationType { } const RunDetailTrace = ({run, runEvents, testId}: IProps) => { - const panels = useMemo( - () => [ - getSpanDetailsPanel(testId, run), - geTracePanel(testId, run, runEvents), - getAnalyzerPanel(run), - ], - [run, runEvents, testId] - ); - return ( - + + + + + ); }; diff --git a/web/src/components/RunDetailTrace/SpanDetailsPanel.tsx b/web/src/components/RunDetailTrace/SpanDetailsPanel.tsx index 72e443cc15..81652f76c9 100644 --- a/web/src/components/RunDetailTrace/SpanDetailsPanel.tsx +++ b/web/src/components/RunDetailTrace/SpanDetailsPanel.tsx @@ -4,16 +4,21 @@ import {useAppSelector} from 'redux/hooks'; import TraceSelectors from 'selectors/Trace.selectors'; import TestRun from 'models/TestRun.model'; import SpanSelectors from 'selectors/Span.selectors'; -import {TPanel, TPanelComponentProps} from '../ResizablePanels/ResizablePanels'; import SpanDetail from '../SpanDetail/SpanDetail'; -import * as S from './RunDetailTrace.styled'; +import {LeftPanel, PanelContainer} from '../ResizablePanels'; -type TProps = TPanelComponentProps & { +interface IProps { run: TestRun; testId: string; +} + +const panel = { + name: 'SPAN_DETAILS', + minSize: 15, + maxSize: 320, }; -const SpanDetailsPanel = ({size: {isOpen}, run, testId}: TProps) => { +const SpanDetailsPanel = ({run, testId}: IProps) => { const searchText = useAppSelector(TraceSelectors.selectSearchText); const selectedSpan = useAppSelector(TraceSelectors.selectSelectedSpan); const navigate = useNavigate(); @@ -24,18 +29,14 @@ const SpanDetailsPanel = ({size: {isOpen}, run, testId}: TProps) => { }, [navigate, run.id, testId]); return ( - - - + + {size => ( + + + + )} + ); }; -export const getSpanDetailsPanel = (testId: string, run: TestRun): TPanel => ({ - name: 'SPAN_DETAILS', - minSize: 15, - maxSize: 320, - position: 'left', - component: props => , -}); - export default SpanDetailsPanel; diff --git a/web/src/components/RunDetailTrace/TracePanel.tsx b/web/src/components/RunDetailTrace/TracePanel.tsx index 6fcb4d0bd4..004b9a06f1 100644 --- a/web/src/components/RunDetailTrace/TracePanel.tsx +++ b/web/src/components/RunDetailTrace/TracePanel.tsx @@ -4,11 +4,11 @@ import TraceAnalyticsService from 'services/Analytics/TestRunAnalytics.service'; import {TestState} from 'constants/TestRun.constants'; import TestRunEvent from 'models/TestRunEvent.model'; import Search from './Search'; -import {TPanel} from '../ResizablePanels/ResizablePanels'; import {VisualizationType} from './RunDetailTrace'; import * as S from './RunDetailTrace.styled'; import Switch from '../Visualization/components/Switch/Switch'; import Visualization from './Visualization'; +import {FillPanel} from '../ResizablePanels'; type TProps = { run: TestRun; @@ -20,39 +20,36 @@ const TracePanel = ({run, testId, runEvents}: TProps) => { const [visualizationType, setVisualizationType] = useState(VisualizationType.Dag); return ( - - - - - + + + + + + - - - {run.state === TestState.FINISHED && ( - { - TraceAnalyticsService.onSwitchDiagramView(type); - setVisualizationType(type); - }} - type={visualizationType} - /> - )} - - - - - + + + {run.state === TestState.FINISHED && ( + { + TraceAnalyticsService.onSwitchDiagramView(type); + setVisualizationType(type); + }} + type={visualizationType} + /> + )} + + + + + + ); }; -export const geTracePanel = (testId: string, run: TestRun, runEvents: TestRunEvent[]): TPanel => ({ - name: 'TRACE', - component: () => , -}); - export default TracePanel; diff --git a/web/src/components/Visualization/components/DAG/AnalyzerResults.styled.ts b/web/src/components/Visualization/components/DAG/AnalyzerResults.styled.ts index 324ba73a9f..bb295987e2 100644 --- a/web/src/components/Visualization/components/DAG/AnalyzerResults.styled.ts +++ b/web/src/components/Visualization/components/DAG/AnalyzerResults.styled.ts @@ -19,6 +19,7 @@ export const Connector = styled.div` export const Container = styled.div` border: ${({theme}) => `2px solid ${theme.color.error}`}; + background-color: ${({theme}) => theme.color.white}; border-radius: 10px; font-size: ${({theme}) => theme.size.xs}; height: 100px;