From a6622d97046e171ec0eaa1803e22b1fdb1ed66b6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 5 Dec 2021 21:47:01 +0100 Subject: [PATCH] core-components: document LogViewer + restructure Signed-off-by: Patrik Oldsberg --- .../components/LogViewer/LazyLogViewer.tsx | 32 ----- .../LogViewer/LogViewer.stories.tsx | 4 +- .../src/components/LogViewer/LogViewer.tsx | 132 +++++------------- .../components/LogViewer/RealLogViewer.tsx | 126 +++++++++++++++++ .../src/components/LogViewer/index.ts | 2 +- 5 files changed, 162 insertions(+), 134 deletions(-) delete mode 100644 packages/core-components/src/components/LogViewer/LazyLogViewer.tsx create mode 100644 packages/core-components/src/components/LogViewer/RealLogViewer.tsx diff --git a/packages/core-components/src/components/LogViewer/LazyLogViewer.tsx b/packages/core-components/src/components/LogViewer/LazyLogViewer.tsx deleted file mode 100644 index fb765bbbc4..0000000000 --- a/packages/core-components/src/components/LogViewer/LazyLogViewer.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy, Suspense } from 'react'; -import { useApp } from '@backstage/core-plugin-api'; -import { LogViewerProps } from './LogViewer'; - -const LogViewer = lazy(() => - import('./LogViewer').then(m => ({ default: m.LogViewer })), -); - -export function LazyLogViewer(props: LogViewerProps) { - const { Progress } = useApp().getComponents(); - return ( - }> - - - ); -} diff --git a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx index b7a56e1a7b..255e1d03b8 100644 --- a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx @@ -14,12 +14,14 @@ * limitations under the License. */ -import React from 'react'; +import React, { ComponentType } from 'react'; +import { wrapInTestApp } from '@backstage/test-utils'; import { LogViewer } from './LogViewer'; export default { title: 'Data Display/LogViewer', component: LogViewer, + decorators: [(Story: ComponentType<{}>) => wrapInTestApp()], }; const exampleLog = `Starting up task with 3 steps diff --git a/packages/core-components/src/components/LogViewer/LogViewer.tsx b/packages/core-components/src/components/LogViewer/LogViewer.tsx index 36d25153cd..a85603b465 100644 --- a/packages/core-components/src/components/LogViewer/LogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewer.tsx @@ -14,113 +14,45 @@ * limitations under the License. */ -import React, { useEffect, useMemo, useRef } from 'react'; -import IconButton from '@material-ui/core/IconButton'; -import CopyIcon from '@material-ui/icons/FileCopy'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { FixedSizeList } from 'react-window'; -import { AnsiProcessor } from './AnsiProcessor'; -import { HEADER_SIZE, useStyles } from './styles'; -import clsx from 'clsx'; -import { LogLine } from './LogLine'; -import { LogViewerControls } from './LogViewerControls'; -import { useLogViewerSearch } from './useLogViewerSearch'; -import { useLogViewerSelection } from './useLogViewerSelection'; +import React, { lazy, Suspense } from 'react'; +import { useApp } from '@backstage/core-plugin-api'; +const RealLogViewer = lazy(() => + import('./RealLogViewer').then(m => ({ default: m.RealLogViewer })), +); + +/** + * The properties for the LogViewer component. + */ export interface LogViewerProps { + /** + * The text of the logs to display. + * + * The LogViewer component is optimized for appending content at the end of the text. + */ text: string; + /** + * The className to apply to the root LogViewer element inside the auto sizer. + */ className?: string; } +/** + * A component that displays logs in a scrollable text area. + * + * The LogViewer has support for search and filtering, as well as displaying + * text content with ANSI color escape codes. + * + * Since the LogViewer uses windowing to avoid rendering all contents at once, the + * log is sized automatically to fill the available vertical space. This means + * it may often be needed to wrap the LogViewer in a container that provides it + * with a fixed amount of space. + */ export function LogViewer(props: LogViewerProps) { - const classes = useStyles(); - const listRef = useRef(null); - - // The processor keeps state that optimizes appending to the text - const processor = useMemo(() => new AnsiProcessor(), []); - const lines = processor.process(props.text); - - const search = useLogViewerSearch(lines); - const selection = useLogViewerSelection(lines); - - useEffect(() => { - if (search.resultLine !== undefined && listRef.current) { - listRef.current.scrollToItem(search.resultLine - 1, 'center'); - } - }, [search.resultLine]); - - const handleSelectLine = ( - line: number, - event: { shiftKey: boolean; preventDefault: () => void }, - ) => { - event.preventDefault(); - selection.setSelection(line, event.shiftKey); - }; - + const { Progress } = useApp().getComponents(); return ( - - {({ height, width }) => ( -
-
- -
- - {({ index, style, data }) => { - const line = data[index]; - const { lineNumber } = line; - return ( -
- {selection.shouldShowButton(lineNumber) && ( - selection.copySelection()} - > - - - )} - handleSelectLine(lineNumber, event)} - onKeyPress={event => handleSelectLine(lineNumber, event)} - > - {lineNumber} - - -
- ); - }} -
-
- )} -
+ }> + + ); } diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx new file mode 100644 index 0000000000..d41ce80672 --- /dev/null +++ b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx @@ -0,0 +1,126 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { useEffect, useMemo, useRef } from 'react'; +import IconButton from '@material-ui/core/IconButton'; +import CopyIcon from '@material-ui/icons/FileCopy'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList } from 'react-window'; +import { AnsiProcessor } from './AnsiProcessor'; +import { HEADER_SIZE, useStyles } from './styles'; +import clsx from 'clsx'; +import { LogLine } from './LogLine'; +import { LogViewerControls } from './LogViewerControls'; +import { useLogViewerSearch } from './useLogViewerSearch'; +import { useLogViewerSelection } from './useLogViewerSelection'; + +export interface RealLogViewerProps { + text: string; + className?: string; +} + +export function RealLogViewer(props: RealLogViewerProps) { + const classes = useStyles(); + const listRef = useRef(null); + + // The processor keeps state that optimizes appending to the text + const processor = useMemo(() => new AnsiProcessor(), []); + const lines = processor.process(props.text); + + const search = useLogViewerSearch(lines); + const selection = useLogViewerSelection(lines); + + useEffect(() => { + if (search.resultLine !== undefined && listRef.current) { + listRef.current.scrollToItem(search.resultLine - 1, 'center'); + } + }, [search.resultLine]); + + const handleSelectLine = ( + line: number, + event: { shiftKey: boolean; preventDefault: () => void }, + ) => { + event.preventDefault(); + selection.setSelection(line, event.shiftKey); + }; + + return ( + + {({ height, width }) => ( +
+
+ +
+ + {({ index, style, data }) => { + const line = data[index]; + const { lineNumber } = line; + return ( +
+ {selection.shouldShowButton(lineNumber) && ( + selection.copySelection()} + > + + + )} + handleSelectLine(lineNumber, event)} + onKeyPress={event => handleSelectLine(lineNumber, event)} + > + {lineNumber} + + +
+ ); + }} +
+
+ )} +
+ ); +} diff --git a/packages/core-components/src/components/LogViewer/index.ts b/packages/core-components/src/components/LogViewer/index.ts index aba2e8ea16..839f34f81e 100644 --- a/packages/core-components/src/components/LogViewer/index.ts +++ b/packages/core-components/src/components/LogViewer/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { LazyLogViewer as LogViewer } from './LazyLogViewer'; +export { LogViewer } from './LogViewer'; export type { LogViewerProps } from './LogViewer';