Merge pull request #7865 from SDA-SE/feat/api-docs-customization
Lazy load API definition widgets
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-circleci': patch
|
||||
'@backstage/plugin-github-actions': patch
|
||||
---
|
||||
|
||||
Use `<Progress>` instead of `<LinearProgress />` in combination with `Suspense`.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Lazy load `LazyLog` as it is rarely used.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-api-docs': patch
|
||||
---
|
||||
|
||||
Lazy load all API definition widgets. The widgets use libraries like
|
||||
`swagger-ui`, `graphiql`, and `@asyncapi/react-component` which are quite heavy
|
||||
weight. To improve initial load times, the widgets are only loaded once used.
|
||||
@@ -84,11 +84,19 @@ export const ApiTypeTitle: ({
|
||||
apiEntity: ApiEntity;
|
||||
}) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const AsyncApiDefinitionWidget: ({ definition }: Props_5) => JSX.Element;
|
||||
export const AsyncApiDefinitionWidget: (
|
||||
props: AsyncApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type AsyncApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -100,7 +108,7 @@ export const ConsumedApisCard: ({ variant }: Props_2) => JSX.Element;
|
||||
// Warning: (ae-missing-release-tag) "ConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ConsumingComponentsCard: ({ variant }: Props_6) => JSX.Element;
|
||||
export const ConsumingComponentsCard: ({ variant }: Props_5) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "defaultDefinitionWidgets" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -169,20 +177,34 @@ export const EntityProvidingComponentsCard: ({
|
||||
// @public (undocumented)
|
||||
export const HasApisCard: ({ variant }: Props_3) => JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const OpenApiDefinitionWidget: ({ definition }: Props_8) => JSX.Element;
|
||||
export const OpenApiDefinitionWidget: (
|
||||
props: OpenApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type OpenApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const PlainApiDefinitionWidget: ({
|
||||
definition,
|
||||
language,
|
||||
}: Props_9) => JSX.Element;
|
||||
export const PlainApiDefinitionWidget: (
|
||||
props: PlainApiDefinitionWidgetProps,
|
||||
) => JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type PlainApiDefinitionWidgetProps = {
|
||||
definition: any;
|
||||
language: string;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "ProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
@@ -194,5 +216,5 @@ export const ProvidedApisCard: ({ variant }: Props_4) => JSX.Element;
|
||||
// Warning: (ae-missing-release-tag) "ProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const ProvidingComponentsCard: ({ variant }: Props_7) => JSX.Element;
|
||||
export const ProvidingComponentsCard: ({ variant }: Props_6) => JSX.Element;
|
||||
```
|
||||
|
||||
+4
-4
@@ -16,9 +16,9 @@
|
||||
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
|
||||
import { AsyncApiDefinition } from './AsyncApiDefinition';
|
||||
|
||||
describe('<AsyncApiDefinitionWidget />', () => {
|
||||
describe('<AsyncApiDefinition />', () => {
|
||||
it('renders asyncapi spec', async () => {
|
||||
const definition = `
|
||||
asyncapi: 2.0.0
|
||||
@@ -40,7 +40,7 @@ components:
|
||||
type: string
|
||||
`;
|
||||
const { getByText, getAllByText } = await renderInTestApp(
|
||||
<AsyncApiDefinitionWidget definition={definition} />,
|
||||
<AsyncApiDefinition definition={definition} />,
|
||||
);
|
||||
|
||||
expect(getByText(/Account Service/i)).toBeInTheDocument();
|
||||
@@ -51,7 +51,7 @@ components:
|
||||
|
||||
it('renders error if definition is missing', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<AsyncApiDefinitionWidget definition="" />,
|
||||
<AsyncApiDefinition definition="" />,
|
||||
);
|
||||
expect(getByText(/Error/i)).toBeInTheDocument();
|
||||
expect(getByText(/Document can't be null or falsey/i)).toBeInTheDocument();
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* 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 AsyncApi from '@asyncapi/react-component';
|
||||
import '@asyncapi/react-component/lib/styles/fiori.css';
|
||||
import { alpha, makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
'& .asyncapi': {
|
||||
'font-family': 'inherit',
|
||||
background: 'none',
|
||||
},
|
||||
'& h2': {
|
||||
...theme.typography.h6,
|
||||
},
|
||||
'& .text-teal': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& button': {
|
||||
...theme.typography.button,
|
||||
background: 'none',
|
||||
boxSizing: 'border-box',
|
||||
minWidth: 64,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
transition: theme.transitions.create(
|
||||
['background-color', 'box-shadow', 'border'],
|
||||
{
|
||||
duration: theme.transitions.duration.short,
|
||||
},
|
||||
),
|
||||
padding: '5px 15px',
|
||||
color: theme.palette.primary.main,
|
||||
border: `1px solid ${alpha(theme.palette.primary.main, 0.5)}`,
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
'&.Mui-disabled': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
backgroundColor: alpha(
|
||||
theme.palette.primary.main,
|
||||
theme.palette.action.hoverOpacity,
|
||||
),
|
||||
// Reset on touch devices, it doesn't add specificity
|
||||
'@media (hover: none)': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
},
|
||||
'& .asyncapi__collapse-button:hover': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& button.asyncapi__toggle-button': {
|
||||
'min-width': 'inherit',
|
||||
},
|
||||
'& .asyncapi__info-list li': {
|
||||
'border-color': theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
color: theme.palette.text.primary,
|
||||
'border-color': theme.palette.primary.main,
|
||||
'background-color': theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
'& .asyncapi__info-list li a': {
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
color: theme.palette.getContrastText(theme.palette.primary.main),
|
||||
},
|
||||
},
|
||||
'& .asyncapi__enum': {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
},
|
||||
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .asyncapi__additional-properties-notice': {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
'& .asyncapi__code, .asyncapi__code-pre': {
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
'& .asyncapi__schema-example-header-title': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .asyncapi__table-header': {
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
'& .asyncapi__table-body': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .asyncapi__server-security-flow': {
|
||||
background: theme.palette.background.default,
|
||||
border: 'none',
|
||||
},
|
||||
'& .asyncapi__server-security-flows-list a': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& .asyncapi__table-row--nested': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const AsyncApiDefinition = ({ definition }: Props) => {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AsyncApi schema={definition} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+16
-121
@@ -14,132 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import AsyncApi from '@asyncapi/react-component';
|
||||
import '@asyncapi/react-component/lib/styles/fiori.css';
|
||||
import { fade, makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
'& .asyncapi': {
|
||||
'font-family': 'inherit',
|
||||
background: 'none',
|
||||
},
|
||||
'& h2': {
|
||||
...theme.typography.h6,
|
||||
},
|
||||
'& .text-teal': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& button': {
|
||||
...theme.typography.button,
|
||||
background: 'none',
|
||||
boxSizing: 'border-box',
|
||||
minWidth: 64,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
transition: theme.transitions.create(
|
||||
['background-color', 'box-shadow', 'border'],
|
||||
{
|
||||
duration: theme.transitions.duration.short,
|
||||
},
|
||||
),
|
||||
padding: '5px 15px',
|
||||
color: theme.palette.primary.main,
|
||||
border: `1px solid ${fade(theme.palette.primary.main, 0.5)}`,
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
'&.Mui-disabled': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
border: `1px solid ${theme.palette.primary.main}`,
|
||||
backgroundColor: fade(
|
||||
theme.palette.primary.main,
|
||||
theme.palette.action.hoverOpacity,
|
||||
),
|
||||
// Reset on touch devices, it doesn't add specificity
|
||||
'@media (hover: none)': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
},
|
||||
'&.Mui-disabled': {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
},
|
||||
'& .asyncapi__collapse-button:hover': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& button.asyncapi__toggle-button': {
|
||||
'min-width': 'inherit',
|
||||
},
|
||||
'& .asyncapi__info-list li': {
|
||||
'border-color': theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
color: theme.palette.text.primary,
|
||||
'border-color': theme.palette.primary.main,
|
||||
'background-color': theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
'& .asyncapi__info-list li a': {
|
||||
color: theme.palette.primary.main,
|
||||
'&:hover': {
|
||||
color: theme.palette.getContrastText(theme.palette.primary.main),
|
||||
},
|
||||
},
|
||||
'& .asyncapi__enum': {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
'& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
},
|
||||
'& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .asyncapi__additional-properties-notice': {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
'& .asyncapi__code, .asyncapi__code-pre': {
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
'& .asyncapi__schema-example-header-title': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header':
|
||||
{
|
||||
'background-color': 'inherit',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .asyncapi__table-header': {
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
'& .asyncapi__table-body': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .asyncapi__server-security-flow': {
|
||||
background: theme.palette.background.default,
|
||||
border: 'none',
|
||||
},
|
||||
'& .asyncapi__server-security-flows-list a': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
'& .asyncapi__table-row--nested': {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
},
|
||||
}));
|
||||
// The asyncapi component and related CSS has a significant size, only load it
|
||||
// if the element is actually used.
|
||||
const LazyAsyncApiDefinition = React.lazy(() =>
|
||||
import('./AsyncApiDefinition').then(m => ({
|
||||
default: m.AsyncApiDefinition,
|
||||
})),
|
||||
);
|
||||
|
||||
type Props = {
|
||||
export type AsyncApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const AsyncApiDefinitionWidget = ({ definition }: Props) => {
|
||||
const classes = useStyles();
|
||||
|
||||
export const AsyncApiDefinitionWidget = (
|
||||
props: AsyncApiDefinitionWidgetProps,
|
||||
) => {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AsyncApi schema={definition} />
|
||||
</div>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<LazyAsyncApiDefinition {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget';
|
||||
export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget';
|
||||
|
||||
+3
-3
@@ -16,9 +16,9 @@
|
||||
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget';
|
||||
import { GraphQlDefinition } from './GraphQlDefinition';
|
||||
|
||||
describe('<GraphQlDefinitionWidget />', () => {
|
||||
describe('<GraphQlDefinition />', () => {
|
||||
it('renders graphql schema', async () => {
|
||||
const definition = `
|
||||
"""Hello World!"""
|
||||
@@ -53,7 +53,7 @@ type Film {
|
||||
};
|
||||
|
||||
const { getByText } = await renderInTestApp(
|
||||
<GraphQlDefinitionWidget definition={definition} />,
|
||||
<GraphQlDefinition definition={definition} />,
|
||||
);
|
||||
|
||||
expect(getByText(/Film/i)).toBeInTheDocument();
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 { BackstageTheme } from '@backstage/theme';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import GraphiQL from 'graphiql';
|
||||
import 'graphiql/graphiql.css';
|
||||
import { buildSchema } from 'graphql';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexFlow: 'column nowrap',
|
||||
},
|
||||
graphiQlWrapper: {
|
||||
flex: 1,
|
||||
'@global': {
|
||||
'.graphiql-container': {
|
||||
boxSizing: 'initial',
|
||||
height: '100%',
|
||||
minHeight: '600px',
|
||||
flex: '1 1 auto',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const GraphQlDefinition = ({ definition }: Props) => {
|
||||
const classes = useStyles();
|
||||
const schema = buildSchema(definition);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.graphiQlWrapper}>
|
||||
<GraphiQL
|
||||
fetcher={() => Promise.resolve(null) as any}
|
||||
schema={schema}
|
||||
docExplorerOpen
|
||||
defaultSecondaryEditorOpen={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+14
-41
@@ -14,54 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import 'graphiql/graphiql.css';
|
||||
import { buildSchema } from 'graphql';
|
||||
import React, { Suspense } from 'react';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
const GraphiQL = React.lazy(() => import('graphiql'));
|
||||
// The graphql component, graphql and related CSS has a significant size, only
|
||||
// load it if the element is actually used.
|
||||
const LazyGraphQlDefinition = React.lazy(() =>
|
||||
import('./GraphQlDefinition').then(m => ({
|
||||
default: m.GraphQlDefinition,
|
||||
})),
|
||||
);
|
||||
|
||||
const useStyles = makeStyles<BackstageTheme>(() => ({
|
||||
root: {
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexFlow: 'column nowrap',
|
||||
},
|
||||
graphiQlWrapper: {
|
||||
flex: 1,
|
||||
'@global': {
|
||||
'.graphiql-container': {
|
||||
boxSizing: 'initial',
|
||||
height: '100%',
|
||||
minHeight: '600px',
|
||||
flex: '1 1 auto',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
definition: any;
|
||||
export type GraphQlDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const GraphQlDefinitionWidget = ({ definition }: Props) => {
|
||||
const classes = useStyles();
|
||||
const schema = buildSchema(definition);
|
||||
|
||||
export const GraphQlDefinitionWidget = (
|
||||
props: GraphQlDefinitionWidgetProps,
|
||||
) => {
|
||||
return (
|
||||
<Suspense fallback={<Progress />}>
|
||||
<div className={classes.root}>
|
||||
<div className={classes.graphiQlWrapper}>
|
||||
<GraphiQL
|
||||
fetcher={() => Promise.resolve(null) as any}
|
||||
schema={schema}
|
||||
docExplorerOpen
|
||||
defaultSecondaryEditorOpen={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<LazyGraphQlDefinition {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget';
|
||||
export type { GraphQlDefinitionWidgetProps } from './GraphQlDefinitionWidget';
|
||||
|
||||
+4
-4
@@ -17,9 +17,9 @@
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
|
||||
import { OpenApiDefinition } from './OpenApiDefinition';
|
||||
|
||||
describe('<OpenApiDefinitionWidget />', () => {
|
||||
describe('<OpenApiDefinition />', () => {
|
||||
it('renders openapi spec', async () => {
|
||||
const definition = `
|
||||
openapi: "3.0.0"
|
||||
@@ -39,7 +39,7 @@ paths:
|
||||
description: Success
|
||||
`;
|
||||
const { getByText } = await renderInTestApp(
|
||||
<OpenApiDefinitionWidget definition={definition} />,
|
||||
<OpenApiDefinition definition={definition} />,
|
||||
);
|
||||
|
||||
// swagger-ui loads the documentation asynchronously
|
||||
@@ -51,7 +51,7 @@ paths:
|
||||
|
||||
it('renders error if definition is missing', async () => {
|
||||
const { getByText } = await renderInTestApp(
|
||||
<OpenApiDefinitionWidget definition="" />,
|
||||
<OpenApiDefinition definition="" />,
|
||||
);
|
||||
expect(getByText(/No API definition provided/i)).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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 { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import SwaggerUI from 'swagger-ui-react';
|
||||
import 'swagger-ui-react/swagger-ui.css';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
'& .swagger-ui': {
|
||||
fontFamily: 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
|
||||
[`& .info h1,
|
||||
.info h2,
|
||||
.info h3,
|
||||
.info h4,
|
||||
.info h5,
|
||||
.info h6`]: {
|
||||
fontFamily: 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
[`& .scheme-container`]: {
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
[`& .opblock-tag,
|
||||
.opblock-tag small,
|
||||
table thead tr td,
|
||||
table thead tr th`]: {
|
||||
fontFamily: 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
[`& section.models,
|
||||
section.models.is-open h4`]: {
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
[`& .opblock .opblock-summary-description,
|
||||
.parameter__type,
|
||||
table.headers td,
|
||||
.model-title,
|
||||
.model .property.primitive,
|
||||
section h3`]: {
|
||||
fontFamily: 'inherit',
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
[`& .opblock .opblock-summary-operation-id,
|
||||
.opblock .opblock-summary-path,
|
||||
.opblock .opblock-summary-path__deprecated,
|
||||
.opblock .opblock-section-header h4,
|
||||
.parameter__name,
|
||||
.response-col_status,
|
||||
.response-col_links,
|
||||
.responses-inner h4,
|
||||
.responses-inner h5,
|
||||
.opblock-section-header .btn,
|
||||
.tab li,
|
||||
.info li,
|
||||
.info p,
|
||||
.info table,
|
||||
section.models h4,
|
||||
.info .title,
|
||||
table.model tr.description,
|
||||
.property-row`]: {
|
||||
fontFamily: 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
[`& .opblock .opblock-section-header,
|
||||
.model-box,
|
||||
section.models .model-container`]: {
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
[`& .prop-format,
|
||||
.parameter__in`]: {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
[`& .opblock-description-wrapper p,
|
||||
.opblock-external-docs-wrapper p,
|
||||
.opblock-title_normal p,
|
||||
.response-control-media-type__accept-message,
|
||||
.opblock .opblock-section-header>label,
|
||||
.scheme-container .schemes>label,
|
||||
.info .base-url,
|
||||
.model`]: {
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
[`& .parameter__name.required:after`]: {
|
||||
color: theme.palette.warning.dark,
|
||||
},
|
||||
[`& .prop-type`]: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export type OpenApiDefinitionProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const OpenApiDefinition = ({ definition }: OpenApiDefinitionProps) => {
|
||||
const classes = useStyles();
|
||||
|
||||
// Due to a bug in the swagger-ui-react component, the component needs
|
||||
// to be created without content first.
|
||||
const [def, setDef] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDef(definition), 0);
|
||||
return () => clearTimeout(timer);
|
||||
}, [definition, setDef]);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<SwaggerUI spec={def} deepLinking />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+16
-70
@@ -14,81 +14,27 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import SwaggerUI from 'swagger-ui-react';
|
||||
import 'swagger-ui-react/swagger-ui.css';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
// TODO: Schemas
|
||||
// The swagger-ui component and related CSS has a significant size, only load it
|
||||
// if the element is actually used.
|
||||
const LazyOpenApiDefinition = React.lazy(() =>
|
||||
import('./OpenApiDefinition').then(m => ({
|
||||
default: m.OpenApiDefinition,
|
||||
})),
|
||||
);
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
'& .swagger-ui, .info h1, .info h2, .info h3, .info h4, .info h': {
|
||||
'font-family': 'inherit',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .scheme-container': {
|
||||
'background-color': theme.palette.background.default,
|
||||
},
|
||||
'& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th':
|
||||
{
|
||||
color: theme.palette.text.primary,
|
||||
'border-color': theme.palette.divider,
|
||||
},
|
||||
'& section.models, section.models.is-open h4': {
|
||||
'border-color': theme.palette.divider,
|
||||
},
|
||||
'& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3':
|
||||
{
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
'& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row':
|
||||
{
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
'& .opblock .opblock-section-header, .model-box, section.models .model-container':
|
||||
{
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
'& .prop-format, .parameter__in': {
|
||||
color: theme.palette.text.disabled,
|
||||
},
|
||||
'& ': {
|
||||
color: theme.palette.text.primary,
|
||||
'border-color': theme.palette.divider,
|
||||
},
|
||||
'& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model':
|
||||
{
|
||||
color: theme.palette.text.hint,
|
||||
},
|
||||
'& .parameter__name.required:after': {
|
||||
color: theme.palette.warning.dark,
|
||||
},
|
||||
'& .prop-type': {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = {
|
||||
export type OpenApiDefinitionWidgetProps = {
|
||||
definition: string;
|
||||
};
|
||||
|
||||
export const OpenApiDefinitionWidget = ({ definition }: Props) => {
|
||||
const classes = useStyles();
|
||||
|
||||
// Due to a bug in the swagger-ui-react component, the component needs
|
||||
// to be created without content first.
|
||||
const [def, setDef] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDef(definition), 0);
|
||||
return () => clearTimeout(timer);
|
||||
}, [definition, setDef]);
|
||||
|
||||
export const OpenApiDefinitionWidget = (
|
||||
props: OpenApiDefinitionWidgetProps,
|
||||
) => {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<SwaggerUI spec={def} deepLinking />
|
||||
</div>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<LazyOpenApiDefinition {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget';
|
||||
export type { OpenApiDefinitionWidgetProps } from './OpenApiDefinitionWidget';
|
||||
|
||||
+9
-3
@@ -17,13 +17,19 @@
|
||||
import React from 'react';
|
||||
import { CodeSnippet } from '@backstage/core-components';
|
||||
|
||||
type Props = {
|
||||
export type PlainApiDefinitionWidgetProps = {
|
||||
definition: any;
|
||||
language: string;
|
||||
};
|
||||
|
||||
export const PlainApiDefinitionWidget = ({ definition, language }: Props) => {
|
||||
export const PlainApiDefinitionWidget = (
|
||||
props: PlainApiDefinitionWidgetProps,
|
||||
) => {
|
||||
return (
|
||||
<CodeSnippet text={definition} language={language} showCopyCodeButton />
|
||||
<CodeSnippet
|
||||
text={props.definition}
|
||||
language={props.language}
|
||||
showCopyCodeButton
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { PlainApiDefinitionWidget } from './PlainApiDefinitionWidget';
|
||||
export type { PlainApiDefinitionWidgetProps } from './PlainApiDefinitionWidget';
|
||||
|
||||
+2
-2
@@ -14,11 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
LinearProgress,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
@@ -85,7 +85,7 @@ export const ActionOutput = ({
|
||||
{messages.length === 0 ? (
|
||||
'Nothing here...'
|
||||
) : (
|
||||
<Suspense fallback={<LinearProgress />}>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<div style={{ height: '20vh', width: '100%' }}>
|
||||
<LazyLog text={messages.join('\n')} extraLines={1} enableSearch />
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
CircularProgress,
|
||||
Fade,
|
||||
LinearProgress,
|
||||
makeStyles,
|
||||
Modal,
|
||||
Theme,
|
||||
@@ -27,15 +30,11 @@ import {
|
||||
Typography,
|
||||
Zoom,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import React, { Suspense } from 'react';
|
||||
import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs';
|
||||
import { useProjectName } from '../useProjectName';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import DescriptionIcon from '@material-ui/icons/Description';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { readGitHubIntegrationConfigs } from '@backstage/integration';
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import React, { Suspense } from 'react';
|
||||
import { useProjectName } from '../useProjectName';
|
||||
import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs';
|
||||
|
||||
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
|
||||
const LinePart = React.lazy(() => import('react-lazylog/build/LinePart'));
|
||||
@@ -72,7 +71,7 @@ const DisplayLog = ({
|
||||
className: string;
|
||||
}) => {
|
||||
return (
|
||||
<Suspense fallback={<LinearProgress />}>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<div className={className}>
|
||||
<LazyLog
|
||||
text={jobLogs ?? 'No Values Found'}
|
||||
|
||||
@@ -14,38 +14,40 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, memo, useMemo } from 'react';
|
||||
import { makeStyles, Theme, createStyles } from '@material-ui/core/styles';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { useParams } from 'react-router';
|
||||
import { useTaskEventStream } from '../hooks/useEventStream';
|
||||
import LazyLog from 'react-lazylog/build/LazyLog';
|
||||
import {
|
||||
Content,
|
||||
ErrorPage,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Page,
|
||||
Progress,
|
||||
} from '@backstage/core-components';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import {
|
||||
CircularProgress,
|
||||
Paper,
|
||||
StepButton,
|
||||
StepIconProps,
|
||||
} from '@material-ui/core';
|
||||
import { Status, TaskOutput } from '../../types';
|
||||
import { DateTime, Interval } from 'luxon';
|
||||
import { useInterval } from 'react-use';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Cancel from '@material-ui/icons/Cancel';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
|
||||
import classNames from 'classnames';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { DateTime, Interval } from 'luxon';
|
||||
import React, { memo, Suspense, useEffect, useMemo, useState } from 'react';
|
||||
import { useParams } from 'react-router';
|
||||
import { useInterval } from 'react-use';
|
||||
import { Status, TaskOutput } from '../../types';
|
||||
import { useTaskEventStream } from '../hooks/useEventStream';
|
||||
import { TaskPageLinks } from './TaskPageLinks';
|
||||
import {
|
||||
Page,
|
||||
Header,
|
||||
Lifecycle,
|
||||
Content,
|
||||
ErrorPage,
|
||||
} from '@backstage/core-components';
|
||||
|
||||
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
|
||||
|
||||
// typings are wrong for this library, so fallback to not parsing types.
|
||||
const humanizeDuration = require('humanize-duration');
|
||||
@@ -213,9 +215,17 @@ export const TaskStatusStepper = memo(
|
||||
|
||||
const TaskLogger = memo(({ log }: { log: string }) => {
|
||||
return (
|
||||
<div style={{ height: '80vh' }}>
|
||||
<LazyLog text={log} extraLines={1} follow selectableLines enableSearch />
|
||||
</div>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<div style={{ height: '80vh' }}>
|
||||
<LazyLog
|
||||
text={log}
|
||||
extraLines={1}
|
||||
follow
|
||||
selectableLines
|
||||
enableSearch
|
||||
/>
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
|
||||
// react-lazylog is based on a react-virtualized component which doesn't
|
||||
// write the content to the dom, so we mock it.
|
||||
jest.mock('react-lazylog', () => {
|
||||
jest.mock('react-lazylog/build/LazyLog', () => {
|
||||
return {
|
||||
LazyLog: ({ text }: { text: string }) => {
|
||||
default: ({ text }: { text: string }) => {
|
||||
return <p>{text}</p>;
|
||||
},
|
||||
};
|
||||
@@ -46,18 +46,20 @@ describe('<TechDocsBuildLogs />', () => {
|
||||
});
|
||||
|
||||
describe('<TechDocsBuildLogsDrawerContent />', () => {
|
||||
it('should render with empty log', () => {
|
||||
it('should render with empty log', async () => {
|
||||
const onClose = jest.fn();
|
||||
const rendered = render(
|
||||
<TechDocsBuildLogsDrawerContent buildLog={[]} onClose={onClose} />,
|
||||
);
|
||||
expect(rendered.getByText(/Build Details/i)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/Waiting for logs.../i)).toBeInTheDocument();
|
||||
expect(
|
||||
await rendered.findByText(/Waiting for logs.../i),
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(onClose).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should render with empty logs', () => {
|
||||
it('should render logs', async () => {
|
||||
const onClose = jest.fn();
|
||||
const rendered = render(
|
||||
<TechDocsBuildLogsDrawerContent
|
||||
@@ -66,11 +68,8 @@ describe('<TechDocsBuildLogsDrawerContent />', () => {
|
||||
/>,
|
||||
);
|
||||
expect(rendered.getByText(/Build Details/i)).toBeInTheDocument();
|
||||
expect(
|
||||
rendered.queryByText(/Waiting for logs.../i),
|
||||
).not.toBeInTheDocument();
|
||||
expect(rendered.getByText(/Line 1/i)).toBeInTheDocument();
|
||||
expect(rendered.getByText(/Line 2/i)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(/Line 1/i)).toBeInTheDocument();
|
||||
expect(await rendered.findByText(/Line 2/i)).toBeInTheDocument();
|
||||
|
||||
expect(onClose).toBeCalledTimes(0);
|
||||
});
|
||||
@@ -80,7 +79,7 @@ describe('<TechDocsBuildLogsDrawerContent />', () => {
|
||||
const rendered = render(
|
||||
<TechDocsBuildLogsDrawerContent buildLog={[]} onClose={onClose} />,
|
||||
);
|
||||
rendered.getByRole('button').click();
|
||||
rendered.getByTitle('Close the drawer').click();
|
||||
|
||||
expect(onClose).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Progress } from '@backstage/core-components';
|
||||
import {
|
||||
Button,
|
||||
createStyles,
|
||||
@@ -25,9 +26,9 @@ import {
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import Close from '@material-ui/icons/Close';
|
||||
import * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { LazyLog } from 'react-lazylog';
|
||||
import React, { Suspense, useState } from 'react';
|
||||
|
||||
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
|
||||
|
||||
const useDrawerStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -83,15 +84,17 @@ export const TechDocsBuildLogsDrawerContent = ({
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
||||
<LazyLog
|
||||
text={
|
||||
buildLog.length === 0 ? 'Waiting for logs...' : buildLog.join('\n')
|
||||
}
|
||||
extraLines={1}
|
||||
follow
|
||||
selectableLines
|
||||
enableSearch
|
||||
/>
|
||||
<Suspense fallback={<Progress />}>
|
||||
<LazyLog
|
||||
text={
|
||||
buildLog.length === 0 ? 'Waiting for logs...' : buildLog.join('\n')
|
||||
}
|
||||
extraLines={1}
|
||||
follow
|
||||
selectableLines
|
||||
enableSearch
|
||||
/>
|
||||
</Suspense>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user