core-components: added new LogViewer component

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-12-04 15:56:47 +01:00
committed by blam
parent d94abcab7d
commit cef1f124f6
6 changed files with 305 additions and 12 deletions
+4
View File
@@ -62,6 +62,8 @@
"react-syntax-highlighter": "^15.4.3",
"react-text-truncate": "^0.16.0",
"react-use": "^17.2.4",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.6",
"remark-gfm": "^2.0.0",
"zen-observable": "^0.8.15"
},
@@ -89,6 +91,8 @@
"@types/node": "^14.14.32",
"@types/react-helmet": "^6.1.0",
"@types/react-syntax-highlighter": "^13.5.2",
"@types/react-virtualized-auto-sizer": "^1.0.1",
"@types/react-window": "^1.8.5",
"@types/zen-observable": "^0.8.0"
},
"files": [
@@ -0,0 +1,81 @@
/*
* Copyright 2020 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 from 'react';
import { LogViewer } from './LogViewer';
export default {
title: 'Data Display/LogViewer',
component: LogViewer,
};
const exampleLog = `Starting up task with 3 steps
Beginning step Fetch Skeleton + Template
info: Fetching template content from remote URL {"timestamp":"2021-12-03T15:47:11.625Z"}
info: Listing files and directories in template {"timestamp":"2021-12-03T15:47:12.797Z"}
info: Processing 33 template files/directories with input values {"component_id":"srnthsrthntrhsn","description":"rnthsrtnhssrthnrsthn","destination":{"host":"github.com","owner":"rtshnsrtmhrstmh","repo":"srtmhsrtmhrsthms"},"owner":"rstnhrstnhsrthn","timestamp":"2021-12-03T15:47:12.801Z"}
info: Writing file .editorconfig to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.816Z"}
info: Writing file .eslintignore to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.818Z"}
info: Writing file .eslintrc.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.820Z"}
info: Writing directory .github/ to template output path. {"timestamp":"2021-12-03T15:47:12.823Z"}
info: Writing file .gitignore to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.824Z"}
info: Writing file README.md to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.827Z"}
info: Writing file babel.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.829Z"}
info: Writing file catalog-info.yaml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.831Z"}
info: Writing directory docs/ to template output path. {"timestamp":"2021-12-03T15:47:12.834Z"}
info: Writing file jest.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.836Z"}
info: Writing file mkdocs.yml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.838Z"}
info: Writing file next-env.d.ts to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.841Z"}
info: Writing file next.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.844Z"}
info: Writing file package.json to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.845Z"}
info: Writing file prettier.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.848Z"}
info: Writing directory public/ to template output path. {"timestamp":"2021-12-03T15:47:12.849Z"}
info: Writing directory src/ to template output path. {"timestamp":"2021-12-03T15:47:12.850Z"}
info: Writing file tsconfig.json to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.851Z"}
info: Writing directory .github/workflows/ to template output path. {"timestamp":"2021-12-03T15:47:12.853Z"}
info: Writing file docs/index.md to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.854Z"}
info: Writing directory public/static/ to template output path. {"timestamp":"2021-12-03T15:47:12.857Z"}
info: Writing directory src/__tests__/ to template output path. {"timestamp":"2021-12-03T15:47:12.858Z"}
info: Writing directory src/components/ to template output path. {"timestamp":"2021-12-03T15:47:12.858Z"}
info: Writing directory src/pages/ to template output path. {"timestamp":"2021-12-03T15:47:12.859Z"}
info: Copying file/directory .github/workflows/build.yml without processing. {"timestamp":"2021-12-03T15:47:12.859Z"}
info: Writing file .github/workflows/build.yml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.860Z"}
info: Writing file public/static/fonts.css to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.861Z"}
info: Writing file src/components/Header.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.863Z"}
info: Writing file src/__tests__/index.test.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.865Z"}
info: Writing file src/pages/_app.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.868Z"}
info: Writing file src/pages/_document.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.871Z"}
info: Writing directory src/pages/api/ to template output path. {"timestamp":"2021-12-03T15:47:12.873Z"}
info: Writing file src/pages/index.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.874Z"}
info: Writing file src/pages/api/ping.ts to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.877Z"}
info: Template result written to /var/folders/k6/9s7hd6w17115xlgwnsp0wsbr0000gn/T/5c9f8584-fded-4741-b6ef-46d94ff2cbdb {"timestamp":"2021-12-03T15:47:12.878Z"}
Finished step Fetch Skeleton + Template
Beginning step Publish
HttpError: Not Found
at /Users/patriko/dev/backstage/node_modules/@octokit/request/dist-node/index.js:86:21
at runMicrotasks (<anonymous>)
at processTicksAndRejections (internal/process/task_queues.js:95:5)
at async Object.handler (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts:156:20)
at async HandlebarsWorkflowRunner.execute (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts:254:11)
at async TaskWorker.runOneTask (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts:110:13)
at async eval (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts:100:9)
Run completed with status: failed`;
export const ExampleLogViewer = () => (
<div style={{ height: 240 }}>
<LogViewer text={exampleLog} />
</div>
);
@@ -0,0 +1,196 @@
/*
* 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 { makeStyles } from '@material-ui/core/styles';
import React, { useMemo } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import { FixedSizeList } from 'react-window';
import { AnsiProcessor } from './AnsiProcessor';
import startCase from 'lodash/startCase';
import * as colors from '@material-ui/core/colors';
export interface LogViewerProps {
text: string;
noLineNumbers?: boolean;
}
export type AnsiColor =
| 'black'
| 'red'
| 'green'
| 'yellow'
| 'blue'
| 'magenta'
| 'cyan'
| 'white'
| 'grey';
export interface ChunkModifiers {
foreground?: AnsiColor;
background?: AnsiColor;
bold?: boolean;
italic?: boolean;
underline?: boolean;
}
const useStyles = makeStyles(theme => ({
root: {
fontFamily: '"Monaco", monospace',
fontSize: theme.typography.fontSize,
background: theme.palette.background.paper,
},
line: {
whiteSpace: 'pre',
},
lineNumber: {
display: 'inline-block',
textAlign: 'end',
width: 60,
marginRight: theme.spacing(1),
},
modifierBold: {
fontWeight: theme.typography.fontWeightBold,
},
modifierItalic: {
fontStyle: 'italic',
},
modifierUnderline: {
textDecoration: 'underline',
},
modifierForegroundBlack: {
color: colors.common.black,
},
modifierForegroundRed: {
color: colors.red[500],
},
modifierForegroundGreen: {
color: colors.green[500],
},
modifierForegroundYellow: {
color: colors.yellow[500],
},
modifierForegroundBlue: {
color: colors.blue[500],
},
modifierForegroundMagenta: {
color: colors.purple[500],
},
modifierForegroundCyan: {
color: colors.cyan[500],
},
modifierForegroundWhite: {
color: colors.common.white,
},
modifierForegroundGrey: {
color: colors.grey[500],
},
modifierBackgroundBlack: {
color: colors.common.black,
},
modifierBackgroundRed: {
color: colors.red[500],
},
modifierBackgroundGreen: {
color: colors.green[500],
},
modifierBackgroundYellow: {
color: colors.yellow[500],
},
modifierBackgroundBlue: {
color: colors.blue[500],
},
modifierBackgroundMagenta: {
color: colors.purple[500],
},
modifierBackgroundCyan: {
color: colors.cyan[500],
},
modifierBackgroundWhite: {
color: colors.common.white,
},
modifierBackgroundGrey: {
color: colors.grey[500],
},
}));
function getModifierClasses(
classes: ReturnType<typeof useStyles>,
modifiers: ChunkModifiers,
) {
const classNames = new Array<string>();
if (modifiers.bold) {
classNames.push(classes.modifierBold);
}
if (modifiers.italic) {
classNames.push(classes.modifierItalic);
}
if (modifiers.underline) {
classNames.push(classes.modifierUnderline);
}
if (modifiers.foreground) {
const key = `modifierForeground${startCase(
modifiers.foreground,
)}` as keyof typeof classes;
classNames.push(classes[key]);
}
if (modifiers.background) {
const key = `modifierBackground${startCase(
modifiers.background,
)}` as keyof typeof classes;
classNames.push(classes[key]);
}
return classNames.join(' ');
}
export function LogViewer(props: LogViewerProps) {
const { noLineNumbers } = props;
const classes = useStyles();
// The processor keeps state that optimizes appending to the text
const processor = useMemo(() => new AnsiProcessor(), []);
const lines = processor.process(props.text);
return (
<AutoSizer>
{({ height, width }) => (
<FixedSizeList
className={classes.root}
height={height}
width={width}
itemData={lines}
itemSize={20}
itemCount={lines.length}
>
{({ index, style, data }) => (
<div style={{ ...style }} className={classes.line}>
{!noLineNumbers && (
<span className={classes.lineNumber}>{index + 1}</span>
)}
{data[index].map(({ text, modifiers }, i) => (
<span
key={i}
className={getModifierClasses(classes, modifiers)}
>
{text}
</span>
))}
</div>
)}
</FixedSizeList>
)}
</AutoSizer>
);
}
@@ -0,0 +1,18 @@
/*
* 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.
*/
export { LogViewer } from './LogViewer';
export type { LogViewerProps } from './LogViewer';
@@ -30,6 +30,7 @@ export * from './HeaderIconLinkRow';
export * from './HorizontalScrollGrid';
export * from './Lifecycle';
export * from './Link';
export * from './LogViewer';
export * from './MarkdownContent';
export * from './OAuthRequestDialog';
export * from './OverflowTooltip';
+5 -12
View File
@@ -6974,13 +6974,6 @@
dependencies:
"@types/node" "*"
"@types/ansi-regex@^5.0.0":
version "5.0.0"
resolved "https://registry.npmjs.org/@types/ansi-regex/-/ansi-regex-5.0.0.tgz#569a5189a92cc46d63fb2ad91e6b130f33d999c1"
integrity sha512-SQafVL3pXFh/5qq/nN6p5858g//zSVzcb8JzCLtoVxm8YNPggMQfEIm7aaTNysxpw1S+lFTaW8kv+aR0/CEhCA==
dependencies:
ansi-regex "*"
"@types/archiver@^5.1.0":
version "5.3.0"
resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.0.tgz#2b34ba56d4d7102d256b922c7e91e09eab79db6f"
@@ -9242,11 +9235,6 @@ ansi-html@0.0.7, ansi-html@^0.0.7:
resolved "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
ansi-regex@*, ansi-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
@@ -9267,6 +9255,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-regex@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"