break circular imports

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-12-10 15:13:41 +01:00
parent a65f2d5714
commit c36bd35798
15 changed files with 361 additions and 482 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog-react': patch
'@backstage/plugin-config-schema': patch
---
Internal refactor to break potential circular imports
@@ -31,7 +31,7 @@ import {
useEntityList,
} from '../../hooks/useEntityListProvider';
import { EntityFilter } from '../../types';
import { reduceBackendCatalogFilters } from '../../utils';
import { reduceBackendCatalogFilters } from '../../utils/filters';
/** @public */
export type AllowedEntityFilters<T extends DefaultEntityFilters> = {
@@ -20,7 +20,7 @@ import { useMemo, useRef } from 'react';
import useAsync from 'react-use/esm/useAsync';
import { catalogApiRef } from '../../api';
import { useEntityList } from '../../hooks';
import { reduceCatalogFilters } from '../../utils';
import { reduceCatalogFilters } from '../../utils/filters';
export function useAllEntitiesCount() {
const catalogApi = useApi(catalogApiRef);
@@ -21,7 +21,7 @@ import useAsync from 'react-use/esm/useAsync';
import { catalogApiRef } from '../../api';
import { EntityOwnerFilter, EntityUserFilter } from '../../filters';
import { useEntityList } from '../../hooks';
import { CatalogFilters, reduceCatalogFilters } from '../../utils';
import { CatalogFilters, reduceCatalogFilters } from '../../utils/filters';
import useAsyncFn from 'react-use/esm/useAsyncFn';
import useDeepCompareEffect from 'react-use/esm/useDeepCompareEffect';
@@ -23,7 +23,7 @@ import useAsync from 'react-use/esm/useAsync';
import { catalogApiRef } from '../../api';
import { EntityUserFilter } from '../../filters';
import { useEntityList, useStarredEntities } from '../../hooks';
import { reduceCatalogFilters } from '../../utils';
import { reduceCatalogFilters } from '../../utils/filters';
export function useStarredEntitiesCount() {
const catalogApi = useApi(catalogApiRef);
+1 -1
View File
@@ -22,7 +22,7 @@ import {
} from '@backstage/catalog-model';
import { AlphaEntity } from '@backstage/catalog-model/alpha';
import { EntityFilter, UserListFilterKind } from './types';
import { getEntityRelations } from './utils';
import { getEntityRelations } from './utils/getEntityRelations';
/**
* Filter entities based on Kind.
@@ -48,7 +48,7 @@ import {
reduceBackendCatalogFilters,
reduceCatalogFilters,
reduceEntityFilters,
} from '../utils';
} from '../utils/filters';
import { useApi } from '@backstage/core-plugin-api';
import { QueryEntitiesResponse } from '@backstage/catalog-client';
+1 -1
View File
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './filters';
export { getEntityRelations } from './getEntityRelations';
export { getEntitySourceLocation } from './getEntitySourceLocation';
export type { EntitySourceLocation } from './getEntitySourceLocation';
@@ -1,60 +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 Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import { Schema } from 'jsonschema';
import React from 'react';
import { ChildView } from './ChildView';
import { MetadataView } from './MetadataView';
import { SchemaViewProps } from './types';
export function ArrayView({ path, depth, schema }: SchemaViewProps) {
const itemDepth = depth + 1;
const itemPath = path ? `${path}[]` : '[]';
const itemSchema = schema.items;
return (
<>
<Box marginBottom={4}>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</Box>
<Typography variant="overline">Items</Typography>
<ChildView
lastChild
path={itemPath}
depth={itemDepth}
schema={itemSchema as Schema | undefined}
/>
{schema.additionalItems && schema.additionalItems !== true && (
<>
<Typography variant="overline">Additional Items</Typography>
<ChildView
path={itemPath}
depth={itemDepth}
schema={schema.additionalItems}
lastChild
/>
</>
)}
</>
);
}
@@ -1,127 +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 { JsonValue } from '@backstage/types';
import Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import Divider from '@material-ui/core/Divider';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { Schema } from 'jsonschema';
import React, { useEffect, useRef } from 'react';
import { useScrollTargets } from '../ScrollTargetsContext/ScrollTargetsContext';
import { SchemaView } from './SchemaView';
export interface MetadataViewRowProps {
label: string;
text?: string;
data?: JsonValue;
}
function titleVariant(depth: number) {
if (depth <= 1) {
return 'h2';
} else if (depth === 2) {
return 'h3';
} else if (depth === 3) {
return 'h4';
} else if (depth === 4) {
return 'h5';
}
return 'h6';
}
const useChildViewStyles = makeStyles(theme => ({
title: {
marginBottom: 0,
},
chip: {
marginLeft: theme.spacing(1),
marginRight: 0,
marginBottom: 0,
},
}));
export function ChildView({
path,
depth,
schema,
required,
lastChild,
}: {
path: string;
depth: number;
schema?: Schema;
required?: boolean;
lastChild?: boolean;
}) {
const classes = useChildViewStyles();
const titleRef = useRef<HTMLElement>(null);
const scroll = useScrollTargets();
useEffect(() => {
return scroll?.setScrollListener(path, () => {
titleRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}, [scroll, path]);
const chips = new Array<JSX.Element>();
const chipProps = { size: 'small' as const, classes: { root: classes.chip } };
if (required) {
chips.push(
<Chip label="required" color="default" key="required" {...chipProps} />,
);
}
const visibility = (schema as { visibility?: string })?.visibility;
if (visibility === 'frontend') {
chips.push(
<Chip label="frontend" color="primary" key="visibility" {...chipProps} />,
);
} else if (visibility === 'secret') {
chips.push(
<Chip label="secret" color="secondary" key="visibility" {...chipProps} />,
);
}
return (
<Box paddingBottom={lastChild ? 4 : 8} display="flex" flexDirection="row">
<Divider orientation="vertical" flexItem />
<Box paddingLeft={2} flex={1}>
<Box
display="flex"
flexDirection="row"
marginBottom={2}
alignItems="center"
>
<Typography
ref={titleRef}
variant={titleVariant(depth)}
classes={{ root: classes.title }}
>
{path}
</Typography>
{chips.length > 0 && <Box marginLeft={1} />}
{chips}
</Box>
{schema && (
<SchemaView path={path} depth={depth} schema={schema as Schema} />
)}
</Box>
</Box>
);
}
@@ -1,46 +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 Typography from '@material-ui/core/Typography';
import { Schema } from 'jsonschema';
import React from 'react';
import { ChildView } from './ChildView';
export function MatchView({
path,
depth,
schema,
label,
}: {
path: string;
depth: number;
schema: Schema[];
label: string;
}) {
return (
<>
<Typography variant="overline">{label}</Typography>
{schema.map((optionSchema, index) => (
<ChildView
key={index}
path={`${path}/${index + 1}`}
depth={depth + 1}
schema={optionSchema}
lastChild={index === schema.length - 1}
/>
))}
</>
);
}
@@ -1,108 +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 { JsonValue } from '@backstage/types';
import Paper from '@material-ui/core/Paper';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableRow from '@material-ui/core/TableRow';
import Typography from '@material-ui/core/Typography';
import { Schema } from 'jsonschema';
import React from 'react';
export interface MetadataViewRowProps {
label: string;
text?: string;
data?: JsonValue;
}
export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) {
if (text === undefined && data === undefined) {
return null;
}
return (
<TableRow>
<TableCell style={{ width: 160 }}>
<Typography variant="body1" noWrap style={{ fontWeight: 900 }}>
{label}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body1">
{data ? JSON.stringify(data) : text}
</Typography>
</TableCell>
</TableRow>
);
}
export function MetadataView({ schema }: { schema: Schema }) {
return (
<Paper variant="outlined" square style={{ width: '100%' }}>
<Table size="small">
<TableBody>
<MetadataViewRow label="Type" data={schema.type} />
<MetadataViewRow label="Allowed values" data={schema.enum} />
{schema.additionalProperties === true && (
<MetadataViewRow label="Additional Properties" text="true" />
)}
{schema.additionalItems === true && (
<MetadataViewRow label="Additional Items" text="true" />
)}
<MetadataViewRow label="Format" text={schema.format} />
<MetadataViewRow
label="Pattern"
text={schema.pattern && String(schema.pattern)}
/>
<MetadataViewRow label="Minimum" data={schema.minimum} />
<MetadataViewRow label="Maximum" data={schema.maximum} />
<MetadataViewRow
label="Exclusive minimum"
data={schema.exclusiveMinimum}
/>
<MetadataViewRow
label="Exclusive maximum"
data={schema.exclusiveMaximum}
/>
<MetadataViewRow label="Multiple of" data={schema.multipleOf} />
<MetadataViewRow
label="Maximum number of items"
data={schema.maxItems}
/>
<MetadataViewRow
label="Minimum number of items"
data={schema.minItems}
/>
<MetadataViewRow
label="Maximum number of properties"
data={schema.maxProperties}
/>
<MetadataViewRow
label="Minimum number of properties"
data={schema.minProperties}
/>
<MetadataViewRow label="Maximum Length" data={schema.maxLength} />
<MetadataViewRow label="Minimum Length" data={schema.minLength} />
<MetadataViewRow
label="Items must be unique"
data={schema.uniqueItems}
/>
</TableBody>
</Table>
</Paper>
);
}
@@ -1,95 +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 Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { ChildView } from './ChildView';
import { MetadataView } from './MetadataView';
import { SchemaViewProps } from './types';
function isRequired(name: string, required?: boolean | string[]) {
if (required === true) {
return true;
}
if (Array.isArray(required)) {
return required.includes(name);
}
return false;
}
export function ObjectView({ path, depth, schema }: SchemaViewProps) {
const properties = Object.entries(schema.properties ?? {});
const patternProperties = Object.entries(schema.patternProperties ?? {});
return (
<>
{depth > 0 && (
<Box marginBottom={4}>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</Box>
)}
{properties.length > 0 && (
<>
{depth > 0 && <Typography variant="overline">Properties</Typography>}
{properties.map(([name, propSchema], index) => (
<ChildView
key={name}
path={path ? `${path}.${name}` : name}
depth={depth + 1}
schema={propSchema}
lastChild={index === properties.length - 1}
required={isRequired(name, schema.required)}
/>
))}
</>
)}
{patternProperties.length > 0 && (
<>
{depth > 0 && (
<Typography variant="overline">Pattern Properties</Typography>
)}
{patternProperties.map(([name, propSchema], index) => (
<ChildView
key={name}
path={path ? `${path}.<${name}>` : name}
depth={depth + 1}
schema={propSchema}
lastChild={index === patternProperties.length - 1}
required={isRequired(name, schema.required)}
/>
))}
</>
)}
{schema.additionalProperties && schema.additionalProperties !== true && (
<>
<Typography variant="overline">Additional Properties</Typography>
<ChildView
path={`${path}.*`}
depth={depth + 1}
schema={schema.additionalProperties}
lastChild
/>
</>
)}
</>
);
}
@@ -1,34 +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 Box from '@material-ui/core/Box';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { MetadataView } from './MetadataView';
import { SchemaViewProps } from './types';
export function ScalarView({ schema }: SchemaViewProps) {
return (
<>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</>
);
}
@@ -14,11 +14,20 @@
* limitations under the License.
*/
import React from 'react';
import { ArrayView } from './ArrayView';
import { MatchView } from './MatchView';
import { ObjectView } from './ObjectView';
import { ScalarView } from './ScalarView';
import { JsonValue } from '@backstage/types';
import Box from '@material-ui/core/Box';
import Chip from '@material-ui/core/Chip';
import Divider from '@material-ui/core/Divider';
import Paper from '@material-ui/core/Paper';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableRow from '@material-ui/core/TableRow';
import Typography from '@material-ui/core/Typography';
import { makeStyles } from '@material-ui/core/styles';
import { Schema } from 'jsonschema';
import React, { useEffect, useRef } from 'react';
import { useScrollTargets } from '../ScrollTargetsContext/ScrollTargetsContext';
import { SchemaViewProps } from './types';
export function SchemaView(props: SchemaViewProps) {
@@ -60,3 +69,337 @@ export function SchemaView(props: SchemaViewProps) {
return <ScalarView {...props} />;
}
}
function ArrayView({ path, depth, schema }: SchemaViewProps) {
const itemDepth = depth + 1;
const itemPath = path ? `${path}[]` : '[]';
const itemSchema = schema.items;
return (
<>
<Box marginBottom={4}>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</Box>
<Typography variant="overline">Items</Typography>
<ChildView
lastChild
path={itemPath}
depth={itemDepth}
schema={itemSchema as Schema | undefined}
/>
{schema.additionalItems && schema.additionalItems !== true && (
<>
<Typography variant="overline">Additional Items</Typography>
<ChildView
path={itemPath}
depth={itemDepth}
schema={schema.additionalItems}
lastChild
/>
</>
)}
</>
);
}
function isRequired(name: string, required?: boolean | string[]) {
if (required === true) {
return true;
}
if (Array.isArray(required)) {
return required.includes(name);
}
return false;
}
function ObjectView({ path, depth, schema }: SchemaViewProps) {
const properties = Object.entries(schema.properties ?? {});
const patternProperties = Object.entries(schema.patternProperties ?? {});
return (
<>
{depth > 0 && (
<Box marginBottom={4}>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</Box>
)}
{properties.length > 0 && (
<>
{depth > 0 && <Typography variant="overline">Properties</Typography>}
{properties.map(([name, propSchema], index) => (
<ChildView
key={name}
path={path ? `${path}.${name}` : name}
depth={depth + 1}
schema={propSchema}
lastChild={index === properties.length - 1}
required={isRequired(name, schema.required)}
/>
))}
</>
)}
{patternProperties.length > 0 && (
<>
{depth > 0 && (
<Typography variant="overline">Pattern Properties</Typography>
)}
{patternProperties.map(([name, propSchema], index) => (
<ChildView
key={name}
path={path ? `${path}.<${name}>` : name}
depth={depth + 1}
schema={propSchema}
lastChild={index === patternProperties.length - 1}
required={isRequired(name, schema.required)}
/>
))}
</>
)}
{schema.additionalProperties && schema.additionalProperties !== true && (
<>
<Typography variant="overline">Additional Properties</Typography>
<ChildView
path={`${path}.*`}
depth={depth + 1}
schema={schema.additionalProperties}
lastChild
/>
</>
)}
</>
);
}
interface MetadataViewRowProps {
label: string;
text?: string;
data?: JsonValue;
}
function titleVariant(depth: number) {
if (depth <= 1) {
return 'h2';
} else if (depth === 2) {
return 'h3';
} else if (depth === 3) {
return 'h4';
} else if (depth === 4) {
return 'h5';
}
return 'h6';
}
const useChildViewStyles = makeStyles(theme => ({
title: {
marginBottom: 0,
},
chip: {
marginLeft: theme.spacing(1),
marginRight: 0,
marginBottom: 0,
},
}));
function ChildView({
path,
depth,
schema,
required,
lastChild,
}: {
path: string;
depth: number;
schema?: Schema;
required?: boolean;
lastChild?: boolean;
}) {
const classes = useChildViewStyles();
const titleRef = useRef<HTMLElement>(null);
const scroll = useScrollTargets();
useEffect(() => {
return scroll?.setScrollListener(path, () => {
titleRef.current?.scrollIntoView({ behavior: 'smooth' });
});
}, [scroll, path]);
const chips = new Array<JSX.Element>();
const chipProps = { size: 'small' as const, classes: { root: classes.chip } };
if (required) {
chips.push(
<Chip label="required" color="default" key="required" {...chipProps} />,
);
}
const visibility = (schema as { visibility?: string })?.visibility;
if (visibility === 'frontend') {
chips.push(
<Chip label="frontend" color="primary" key="visibility" {...chipProps} />,
);
} else if (visibility === 'secret') {
chips.push(
<Chip label="secret" color="secondary" key="visibility" {...chipProps} />,
);
}
return (
<Box paddingBottom={lastChild ? 4 : 8} display="flex" flexDirection="row">
<Divider orientation="vertical" flexItem />
<Box paddingLeft={2} flex={1}>
<Box
display="flex"
flexDirection="row"
marginBottom={2}
alignItems="center"
>
<Typography
ref={titleRef}
variant={titleVariant(depth)}
classes={{ root: classes.title }}
>
{path}
</Typography>
{chips.length > 0 && <Box marginLeft={1} />}
{chips}
</Box>
{schema && (
<SchemaView path={path} depth={depth} schema={schema as Schema} />
)}
</Box>
</Box>
);
}
function MatchView({
path,
depth,
schema,
label,
}: {
path: string;
depth: number;
schema: Schema[];
label: string;
}) {
return (
<>
<Typography variant="overline">{label}</Typography>
{schema.map((optionSchema, index) => (
<ChildView
key={index}
path={`${path}/${index + 1}`}
depth={depth + 1}
schema={optionSchema}
lastChild={index === schema.length - 1}
/>
))}
</>
);
}
function ScalarView({ schema }: SchemaViewProps) {
return (
<>
{schema.description && (
<Box marginBottom={4}>
<Typography variant="body1">{schema.description}</Typography>
</Box>
)}
<MetadataView schema={schema} />
</>
);
}
interface MetadataViewRowProps {
label: string;
text?: string;
data?: JsonValue;
}
function MetadataViewRow({ label, text, data }: MetadataViewRowProps) {
if (text === undefined && data === undefined) {
return null;
}
return (
<TableRow>
<TableCell style={{ width: 160 }}>
<Typography variant="body1" noWrap style={{ fontWeight: 900 }}>
{label}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body1">
{data ? JSON.stringify(data) : text}
</Typography>
</TableCell>
</TableRow>
);
}
function MetadataView({ schema }: { schema: Schema }) {
return (
<Paper variant="outlined" square style={{ width: '100%' }}>
<Table size="small">
<TableBody>
<MetadataViewRow label="Type" data={schema.type} />
<MetadataViewRow label="Allowed values" data={schema.enum} />
{schema.additionalProperties === true && (
<MetadataViewRow label="Additional Properties" text="true" />
)}
{schema.additionalItems === true && (
<MetadataViewRow label="Additional Items" text="true" />
)}
<MetadataViewRow label="Format" text={schema.format} />
<MetadataViewRow
label="Pattern"
text={schema.pattern && String(schema.pattern)}
/>
<MetadataViewRow label="Minimum" data={schema.minimum} />
<MetadataViewRow label="Maximum" data={schema.maximum} />
<MetadataViewRow
label="Exclusive minimum"
data={schema.exclusiveMinimum}
/>
<MetadataViewRow
label="Exclusive maximum"
data={schema.exclusiveMaximum}
/>
<MetadataViewRow label="Multiple of" data={schema.multipleOf} />
<MetadataViewRow
label="Maximum number of items"
data={schema.maxItems}
/>
<MetadataViewRow
label="Minimum number of items"
data={schema.minItems}
/>
<MetadataViewRow
label="Maximum number of properties"
data={schema.maxProperties}
/>
<MetadataViewRow
label="Minimum number of properties"
data={schema.minProperties}
/>
<MetadataViewRow label="Maximum Length" data={schema.maxLength} />
<MetadataViewRow label="Minimum Length" data={schema.minLength} />
<MetadataViewRow
label="Items must be unique"
data={schema.uniqueItems}
/>
</TableBody>
</Table>
</Paper>
);
}