Merge pull request #4210 from backstage/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-4.14.0

chore(deps): bump @typescript-eslint/eslint-plugin from 3.10.1 to 4.14.0
This commit is contained in:
Ben Lambert
2021-01-25 12:55:05 +01:00
committed by GitHub
19 changed files with 558 additions and 570 deletions
+18
View File
@@ -0,0 +1,18 @@
---
'@backstage/cli': minor
---
We've bumped the `@eslint-typescript` packages to the latest, which now add some additional rules that might cause lint failures.
The main one which could become an issue is the [no-use-before-define](https://eslint.org/docs/rules/no-use-before-define) rule.
Every plugin and app has the ability to override these rules if you want to ignore them for now.
You can reset back to the default behaviour by using the following in your own `.eslint.js`
```js
rules: {
'no-use-before-define': 'off'
}
```
Because of the nature of this change, we're unable to provide a grace period for the update :(
+10 -3
View File
@@ -34,12 +34,12 @@ module.exports = {
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
lib: require('./tsconfig.json').compilerOptions.lib,
},
ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'],
rules: {
// TODO(Rugvip): We need to bump @typescript-eslint to v4 to enable these
'@typescript-eslint/no-shadow': 0,
'@typescript-eslint/no-redeclare': 0,
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-redeclare': 'off',
'no-console': 0, // Permitted in console programs
'new-cap': ['error', { capIsNew: false }], // Because Express constructs things e.g. like 'const r = express.Router()'
@@ -79,6 +79,13 @@ module.exports = {
],
},
overrides: [
{
files: ['**/*.ts?(x)'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'no-undef': 'off',
},
},
{
files: ['*.test.*', 'src/setupTests.*', 'dev/**'],
rules: {
+9 -4
View File
@@ -32,7 +32,11 @@ module.exports = {
},
parserOptions: {
ecmaVersion: 2018,
ecmaFeatures: {
jsx: true,
},
sourceType: 'module',
lib: require('./tsconfig.json').compilerOptions.lib,
},
settings: {
react: {
@@ -41,10 +45,9 @@ module.exports = {
},
ignorePatterns: ['.eslintrc.js', '**/dist/**', '**/dist-types/**'],
rules: {
// TODO(Rugvip): We need to bump @typescript-eslint to v4 to enable these
'@typescript-eslint/no-shadow': 0,
'@typescript-eslint/no-redeclare': 0,
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/no-redeclare': 'off',
'no-undef': 'off',
'import/newline-after-import': 'error',
'import/no-duplicates': 'warn',
'import/no-extraneous-dependencies': [
@@ -90,6 +93,8 @@ module.exports = {
rules: {
// Default to not enforcing prop-types in typescript
'react/prop-types': 0,
'@typescript-eslint/no-unused-vars': 'off',
'no-undef': 'off',
},
},
{
+2 -2
View File
@@ -50,8 +50,8 @@
"@types/start-server-webpack-plugin": "^2.2.0",
"@types/webpack-env": "^1.15.2",
"@types/webpack-node-externals": "^2.5.0",
"@typescript-eslint/eslint-plugin": "^v3.10.1",
"@typescript-eslint/parser": "^v3.10.1",
"@typescript-eslint/eslint-plugin": "^v4.14.0",
"@typescript-eslint/parser": "^v4.14.0",
"@yarnpkg/lockfile": "^1.1.0",
"bfj": "^7.0.2",
"chalk": "^4.0.0",
@@ -40,6 +40,21 @@ const nestedListStyle = (theme: Theme) =>
},
});
interface StyleProps extends WithStyles {
children?: React.ReactNode;
}
// Sub Components
const StyledList = withStyles(
listStyle,
)(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
const StyledNestedList = withStyles(
nestedListStyle,
)(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
function renderList(list: Array<any>, nested?: boolean) {
const values = list.map((item: any, index: number) => (
<MetadataListItem key={index}>{toValue(item)}</MetadataListItem>
@@ -100,30 +115,10 @@ function toValue(
return <Fragment>{value}</Fragment>;
}
function mapToItems(info: { [key: string]: string }, options: any) {
return Object.keys(info).map(key => (
<TableItem key={key} title={key} value={info[key]} options={options} />
));
}
interface StyleProps extends WithStyles {
children?: React.ReactNode;
}
// Sub Components
const StyledList = withStyles(
listStyle,
)(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
const StyledNestedList = withStyles(
nestedListStyle,
)(({ classes, children }: StyleProps) => (
<MetadataList classes={classes}>{children}</MetadataList>
));
const ItemValue = ({ value, options }: { value: any; options: any }) => (
<Fragment>{toValue(value, options)}</Fragment>
);
const TableItem = ({
title,
value,
@@ -146,6 +141,12 @@ const TableItem = ({
);
};
function mapToItems(info: { [key: string]: string }, options: any) {
return Object.keys(info).map(key => (
<TableItem key={key} title={key} value={info[key]} options={options} />
));
}
type Props = {
metadata: { [key: string]: any };
dense?: boolean;
@@ -26,6 +26,21 @@ type State = {
errorInfo?: ErrorInfo;
};
type EProps = {
error?: Error;
slackChannel?: string;
children?: React.ReactNode;
};
const Error = ({ slackChannel }: EProps) => {
return (
<div role="alert">
Something went wrong here.{' '}
{slackChannel && <>Please contact {slackChannel} for help.</>}
</div>
);
};
export const ErrorBoundary: ComponentClass<
Props,
State
@@ -56,18 +71,3 @@ export const ErrorBoundary: ComponentClass<
return <Error error={error} slackChannel={slackChannel} />;
}
};
type EProps = {
error?: Error;
slackChannel?: string;
children?: React.ReactNode;
};
const Error = ({ slackChannel }: EProps) => {
return (
<div role="alert">
Something went wrong here.{' '}
{slackChannel && <>Please contact {slackChannel} for help.</>}
</div>
);
};
@@ -20,25 +20,6 @@ import {
} from '@backstage/core';
import { SidebarSearch } from '@backstage/plugin-search';
export const AppSidebar = () => (
<Sidebar>
<SidebarLogo />
<SidebarSearch />
<SidebarDivider />
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="./" text="Home" />
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
<SidebarItem icon={LibraryBooks} to="/docs" text="Docs" />
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
<SidebarDivider />
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
{/* End global nav */}
<SidebarDivider />
<SidebarSpace />
<SidebarDivider />
<SidebarSettings />
</Sidebar>
);
const useSidebarLogoStyles = makeStyles({
root: {
@@ -72,3 +53,23 @@ const SidebarLogo = () => {
</div>
);
};
export const AppSidebar = () => (
<Sidebar>
<SidebarLogo />
<SidebarSearch />
<SidebarDivider />
{/* Global nav, not org-specific */}
<SidebarItem icon={HomeIcon} to="./" text="Home" />
<SidebarItem icon={ExtensionIcon} to="api-docs" text="APIs" />
<SidebarItem icon={LibraryBooks} to="/docs" text="Docs" />
<SidebarItem icon={CreateComponentIcon} to="create" text="Create..." />
<SidebarDivider />
<SidebarItem icon={MapIcon} to="tech-radar" text="Tech Radar" />
{/* End global nav */}
<SidebarDivider />
<SidebarSpace />
<SidebarDivider />
<SidebarSettings />
</Sidebar>
);
@@ -99,18 +99,6 @@ const pickClassName = (
return classes.neutral;
};
const BuildsList = ({ build }: { build?: BuildWithSteps }) => (
<Box>
{build &&
build.steps &&
build.steps.map(
({ name, actions }: { name: string; actions: BuildStepAction[] }) => (
<ActionsList key={name} name={name} actions={actions} />
),
)}
</Box>
);
const ActionsList = ({
actions,
}: {
@@ -132,6 +120,18 @@ const ActionsList = ({
);
};
const BuildsList = ({ build }: { build?: BuildWithSteps }) => (
<Box>
{build &&
build.steps &&
build.steps.map(
({ name, actions }: { name: string; actions: BuildStepAction[] }) => (
<ActionsList key={name} name={name} actions={actions} />
),
)}
</Box>
);
export const BuildWithStepsPage = () => {
const { buildId = '' } = useParams();
const classes = useStyles();
@@ -31,16 +31,6 @@ type CostInsightsHeaderProps = {
alerts: number;
};
export const CostInsightsHeader = (props: CostInsightsHeaderProps) => {
if (!props.hasCostData) {
return <CostInsightsHeaderNoData {...props} />;
}
if (props.alerts) {
return <CostInsightsHeaderAlerts {...props} />;
}
return <CostInsightsHeaderNoAlerts {...props} />;
};
const CostInsightsHeaderNoData = ({
owner,
groups,
@@ -132,3 +122,13 @@ export const CostInsightsHeaderNoGroups = () => {
</>
);
};
export const CostInsightsHeader = (props: CostInsightsHeaderProps) => {
if (!props.hasCostData) {
return <CostInsightsHeaderNoData {...props} />;
}
if (props.alerts) {
return <CostInsightsHeaderAlerts {...props} />;
}
return <CostInsightsHeaderNoAlerts {...props} />;
};
@@ -39,6 +39,24 @@ type CostInsightsNavigationProps = {
products: Maybe<Product[]>;
};
const NavigationMenuItem = ({ navigation, icon, title }: NavigationItem) => {
const classes = useStyles();
const { scrollIntoView } = useScroll(navigation);
return (
<MenuItem
button
data-testid={`menu-item-${navigation}`}
className={classes.menuItem}
onClick={scrollIntoView}
>
<ListItemIcon className={classes.listItemIcon}>{icon}</ListItemIcon>
<ListItemText
primary={<Typography className={classes.title}>{title}</Typography>}
/>
</MenuItem>
);
};
export const CostInsightsNavigation = React.memo(
({ alerts, products }: CostInsightsNavigationProps) => {
const classes = useStyles();
@@ -102,21 +120,3 @@ export const CostInsightsNavigation = React.memo(
);
},
);
const NavigationMenuItem = ({ navigation, icon, title }: NavigationItem) => {
const classes = useStyles();
const { scrollIntoView } = useScroll(navigation);
return (
<MenuItem
button
data-testid={`menu-item-${navigation}`}
className={classes.menuItem}
onClick={scrollIntoView}
>
<ListItemIcon className={classes.listItemIcon}>{icon}</ListItemIcon>
<ListItemText
primary={<Typography className={classes.title}>{title}</Typography>}
/>
</MenuItem>
);
};
@@ -72,26 +72,6 @@ const useStyles = makeStyles<Theme>(theme => ({
},
}));
const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => {
const classes = useStyles();
return (
<Box>
{jobs &&
jobs.total_count > 0 &&
jobs.jobs.map(job => (
<JobListItem
key={job.id}
job={job}
className={
job.status !== 'success' ? classes.failed : classes.success
}
entity={entity}
/>
))}
</Box>
);
};
const getElapsedTime = (start: string, end: string) => {
const diff = moment(moment(end || moment()).diff(moment(start)));
const timeElapsed = diff.format('m [minutes] s [seconds]');
@@ -159,6 +139,26 @@ const JobListItem = ({
);
};
const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => {
const classes = useStyles();
return (
<Box>
{jobs &&
jobs.total_count > 0 &&
jobs.jobs.map(job => (
<JobListItem
key={job.id}
job={job}
className={
job.status !== 'success' ? classes.failed : classes.success
}
entity={entity}
/>
))}
</Box>
);
};
export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => {
const config = useApi(configApiRef);
const projectName = useProjectName(entity);
@@ -47,12 +47,6 @@ const useStyles = makeStyles(theme => ({
},
}));
const Page = () => (
<Content>
<BuildWithStepsView />
</Content>
);
const BuildWithStepsView = () => {
const { owner, repo } = useProjectSlugFromEntity();
const { branch, buildNumber } = useParams();
@@ -129,6 +123,11 @@ const BuildWithStepsView = () => {
</div>
);
};
const Page = () => (
<Content>
<BuildWithStepsView />
</Content>
);
export default Page;
export { BuildWithStepsView as BuildWithSteps };
@@ -43,67 +43,6 @@ type DeploymentsAccordionsProps = {
children?: React.ReactNode;
};
export const DeploymentsAccordions = ({
deploymentResources,
clusterPodNamesWithErrors,
}: DeploymentsAccordionsProps) => {
const isOwnedBy = (
ownerReferences: V1OwnerReference[],
obj: V1Pod | V1ReplicaSet | V1Deployment,
): boolean => {
return ownerReferences?.some(or => or.name === obj.metadata?.name);
};
return (
<Grid
container
direction="column"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.deployments.map((deployment, i) => (
<Grid container item key={i} xs>
{deploymentResources.replicaSets
// Filter out replica sets with no replicas
.filter(rs => rs.status && rs.status.replicas > 0)
// Find the replica sets this deployment owns
.filter(rs =>
isOwnedBy(rs.metadata?.ownerReferences ?? [], deployment),
)
.map((rs, j) => {
// Find the pods this replica set owns and render them in the table
const ownedPods = deploymentResources.pods.filter(pod =>
isOwnedBy(pod.metadata?.ownerReferences ?? [], rs),
);
const matchingHpa = deploymentResources.horizontalPodAutoscalers.find(
(hpa: V1HorizontalPodAutoscaler) => {
return (
(hpa.spec?.scaleTargetRef?.kind ?? '').toLowerCase() ===
'deployment' &&
(hpa.spec?.scaleTargetRef?.name ?? '') ===
(deployment.metadata?.name ?? 'unknown-deployment')
);
},
);
return (
<Grid item key={j} xs>
<DeploymentAccordion
deployment={deployment}
ownedPods={ownedPods}
matchingHpa={matchingHpa}
clusterPodNamesWithErrors={clusterPodNamesWithErrors}
/>
</Grid>
);
})}
</Grid>
))}
</Grid>
);
};
type DeploymentAccordionProps = {
deployment: V1Deployment;
ownedPods: V1Pod[];
@@ -112,33 +51,6 @@ type DeploymentAccordionProps = {
children?: React.ReactNode;
};
const DeploymentAccordion = ({
deployment,
ownedPods,
matchingHpa,
clusterPodNamesWithErrors,
}: DeploymentAccordionProps) => {
const podsWithErrors = ownedPods.filter(p =>
clusterPodNamesWithErrors.has(p.metadata?.name ?? ''),
);
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<DeploymentSummary
deployment={deployment}
numberOfCurrentPods={ownedPods.length}
numberOfPodsWithErrors={podsWithErrors.length}
hpa={matchingHpa}
/>
</AccordionSummary>
<AccordionDetails>
<PodsTable pods={ownedPods} />
</AccordionDetails>
</Accordion>
);
};
type DeploymentSummaryProps = {
deployment: V1Deployment;
numberOfCurrentPods: number;
@@ -219,3 +131,91 @@ const DeploymentSummary = ({
</Grid>
);
};
const DeploymentAccordion = ({
deployment,
ownedPods,
matchingHpa,
clusterPodNamesWithErrors,
}: DeploymentAccordionProps) => {
const podsWithErrors = ownedPods.filter(p =>
clusterPodNamesWithErrors.has(p.metadata?.name ?? ''),
);
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<DeploymentSummary
deployment={deployment}
numberOfCurrentPods={ownedPods.length}
numberOfPodsWithErrors={podsWithErrors.length}
hpa={matchingHpa}
/>
</AccordionSummary>
<AccordionDetails>
<PodsTable pods={ownedPods} />
</AccordionDetails>
</Accordion>
);
};
export const DeploymentsAccordions = ({
deploymentResources,
clusterPodNamesWithErrors,
}: DeploymentsAccordionsProps) => {
const isOwnedBy = (
ownerReferences: V1OwnerReference[],
obj: V1Pod | V1ReplicaSet | V1Deployment,
): boolean => {
return ownerReferences?.some(or => or.name === obj.metadata?.name);
};
return (
<Grid
container
direction="column"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.deployments.map((deployment, i) => (
<Grid container item key={i} xs>
{deploymentResources.replicaSets
// Filter out replica sets with no replicas
.filter(rs => rs.status && rs.status.replicas > 0)
// Find the replica sets this deployment owns
.filter(rs =>
isOwnedBy(rs.metadata?.ownerReferences ?? [], deployment),
)
.map((rs, j) => {
// Find the pods this replica set owns and render them in the table
const ownedPods = deploymentResources.pods.filter(pod =>
isOwnedBy(pod.metadata?.ownerReferences ?? [], rs),
);
const matchingHpa = deploymentResources.horizontalPodAutoscalers.find(
(hpa: V1HorizontalPodAutoscaler) => {
return (
(hpa.spec?.scaleTargetRef?.kind ?? '').toLowerCase() ===
'deployment' &&
(hpa.spec?.scaleTargetRef?.name ?? '') ===
(deployment.metadata?.name ?? 'unknown-deployment')
);
},
);
return (
<Grid item key={j} xs>
<DeploymentAccordion
deployment={deployment}
ownedPods={ownedPods}
matchingHpa={matchingHpa}
clusterPodNamesWithErrors={clusterPodNamesWithErrors}
/>
</Grid>
);
})}
</Grid>
))}
</Grid>
);
};
@@ -86,29 +86,6 @@ const sortBySeverity = (a: DetectedError, b: DetectedError) => {
return 0;
};
export const ErrorReporting = ({ detectedErrors }: ErrorReportingProps) => {
const errors = Array.from(detectedErrors.values())
.flat()
.sort(sortBySeverity);
return (
<>
{errors.length === 0 ? (
<InfoCard title="Error Reporting">
<ErrorEmptyState />
</InfoCard>
) : (
<Table
title="Error Reporting"
data={errors}
columns={columns}
options={{ paging: true, search: false }}
/>
)}
</>
);
};
export const ErrorEmptyState = () => {
return (
<Grid
@@ -133,3 +110,26 @@ export const ErrorEmptyState = () => {
</Grid>
);
};
export const ErrorReporting = ({ detectedErrors }: ErrorReportingProps) => {
const errors = Array.from(detectedErrors.values())
.flat()
.sort(sortBySeverity);
return (
<>
{errors.length === 0 ? (
<InfoCard title="Error Reporting">
<ErrorEmptyState />
</InfoCard>
) : (
<Table
title="Error Reporting"
data={errors}
columns={columns}
options={{ paging: true, search: false }}
/>
)}
</>
);
};
@@ -32,42 +32,10 @@ type IngressesAccordionsProps = {
deploymentResources: GroupedResponses;
};
export const IngressesAccordions = ({
deploymentResources,
}: IngressesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.ingresses.map((ingress, i) => (
<Grid item key={i} xs>
<IngressAccordion ingress={ingress} />
</Grid>
))}
</Grid>
);
};
type IngressAccordionProps = {
ingress: ExtensionsV1beta1Ingress;
};
const IngressAccordion = ({ ingress }: IngressAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<IngressSummary ingress={ingress} />
</AccordionSummary>
<AccordionDetails>
<IngressCard ingress={ingress} />
</AccordionDetails>
</Accordion>
);
};
type IngressSummaryProps = {
ingress: ExtensionsV1beta1Ingress;
};
@@ -99,3 +67,34 @@ const IngressCard = ({ ingress }: IngressCardProps) => {
/>
);
};
const IngressAccordion = ({ ingress }: IngressAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<IngressSummary ingress={ingress} />
</AccordionSummary>
<AccordionDetails>
<IngressCard ingress={ingress} />
</AccordionDetails>
</Accordion>
);
};
export const IngressesAccordions = ({
deploymentResources,
}: IngressesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.ingresses.map((ingress, i) => (
<Grid item key={i} xs>
<IngressAccordion ingress={ingress} />
</Grid>
))}
</Grid>
);
};
@@ -50,6 +50,115 @@ import { DetectedError, detectErrors } from '../../error-detection';
import { IngressesAccordions } from '../IngressesAccordions';
import { ServicesAccordions } from '../ServicesAccordions';
type ClusterSummaryProps = {
clusterName: string;
totalNumberOfPods: number;
numberOfPodsWithErrors: number;
children?: React.ReactNode;
};
const ClusterSummary = ({
clusterName,
totalNumberOfPods,
numberOfPodsWithErrors,
}: ClusterSummaryProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
<Grid
xs={2}
item
direction="column"
justify="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item xs>
<Typography variant="h3">{clusterName}</Typography>
<Typography color="textSecondary" variant="body1">
Cluster
</Typography>
</Grid>
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '4em' }} orientation="vertical" />
</Grid>
<Grid
item
container
xs={3}
direction="column"
justify="flex-start"
alignItems="flex-start"
>
<Grid item>
<StatusOK>{totalNumberOfPods} pods</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>{numberOfPodsWithErrors} pods with errors</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
)}
</Grid>
</Grid>
</Grid>
);
};
type ClusterProps = {
clusterObjects: ClusterObjects;
detectedErrors?: DetectedError[];
children?: React.ReactNode;
};
const Cluster = ({ clusterObjects, detectedErrors }: ClusterProps) => {
const groupedResponses = groupResponses(clusterObjects.resources);
const podsWithErrors = new Set<string>(
detectedErrors
?.filter(de => de.kind === 'Pod')
.map(de => de.names)
.flat() ?? [],
);
return (
<>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ClusterSummary
clusterName={clusterObjects.cluster.name}
totalNumberOfPods={groupedResponses.pods.length}
numberOfPodsWithErrors={podsWithErrors.size}
/>
</AccordionSummary>
<AccordionDetails>
<Grid container direction="column">
<Grid item>
<DeploymentsAccordions
deploymentResources={groupedResponses}
clusterPodNamesWithErrors={podsWithErrors}
/>
</Grid>
<Grid item>
<IngressesAccordions deploymentResources={groupedResponses} />
</Grid>
<Grid item>
<ServicesAccordions deploymentResources={groupedResponses} />
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</>
);
};
type KubernetesContentProps = { entity: Entity; children?: React.ReactNode };
export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
@@ -161,112 +270,3 @@ export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
</Page>
);
};
type ClusterProps = {
clusterObjects: ClusterObjects;
detectedErrors?: DetectedError[];
children?: React.ReactNode;
};
const Cluster = ({ clusterObjects, detectedErrors }: ClusterProps) => {
const groupedResponses = groupResponses(clusterObjects.resources);
const podsWithErrors = new Set<string>(
detectedErrors
?.filter(de => de.kind === 'Pod')
.map(de => de.names)
.flat() ?? [],
);
return (
<>
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ClusterSummary
clusterName={clusterObjects.cluster.name}
totalNumberOfPods={groupedResponses.pods.length}
numberOfPodsWithErrors={podsWithErrors.size}
/>
</AccordionSummary>
<AccordionDetails>
<Grid container direction="column">
<Grid item>
<DeploymentsAccordions
deploymentResources={groupedResponses}
clusterPodNamesWithErrors={podsWithErrors}
/>
</Grid>
<Grid item>
<IngressesAccordions deploymentResources={groupedResponses} />
</Grid>
<Grid item>
<ServicesAccordions deploymentResources={groupedResponses} />
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</>
);
};
type ClusterSummaryProps = {
clusterName: string;
totalNumberOfPods: number;
numberOfPodsWithErrors: number;
children?: React.ReactNode;
};
const ClusterSummary = ({
clusterName,
totalNumberOfPods,
numberOfPodsWithErrors,
}: ClusterSummaryProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
<Grid
xs={2}
item
direction="column"
justify="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item xs>
<Typography variant="h3">{clusterName}</Typography>
<Typography color="textSecondary" variant="body1">
Cluster
</Typography>
</Grid>
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '4em' }} orientation="vertical" />
</Grid>
<Grid
item
container
xs={3}
direction="column"
justify="flex-start"
alignItems="flex-start"
>
<Grid item>
<StatusOK>{totalNumberOfPods} pods</StatusOK>
</Grid>
<Grid item>
{numberOfPodsWithErrors > 0 ? (
<StatusError>{numberOfPodsWithErrors} pods with errors</StatusError>
) : (
<StatusOK>No pods with errors</StatusOK>
)}
</Grid>
</Grid>
</Grid>
);
};
@@ -77,65 +77,6 @@ interface KubernetesDrawerable {
metadata?: V1ObjectMeta;
}
interface KubernetesDrawerProps<T extends KubernetesDrawerable> {
object: T;
renderObject: (obj: T) => object;
buttonVariant?: 'h5' | 'subtitle2';
kind: string;
expanded?: boolean;
children?: React.ReactNode;
}
export const KubernetesDrawer = <T extends KubernetesDrawerable>({
object,
renderObject,
kind,
buttonVariant = 'subtitle2',
expanded = false,
children,
}: KubernetesDrawerProps<T>) => {
const [isOpen, setIsOpen] = useState(expanded);
const classes = useDrawerStyles();
const toggleDrawer = (e: ChangeEvent<{}>, newValue: boolean) => {
e.stopPropagation();
setIsOpen(newValue);
};
return (
<>
<PodDrawerButton
onClick={e => toggleDrawer(e, true)}
onFocus={event => event.stopPropagation()}
>
{children === undefined ? (
<Typography variant={buttonVariant}>
{object.metadata?.name ?? 'unknown object'}
</Typography>
) : (
children
)}
</PodDrawerButton>
<Drawer
classes={{
paper: classes.paper,
}}
anchor="right"
open={isOpen}
onClose={(e: any) => toggleDrawer(e, false)}
onClick={event => event.stopPropagation()}
>
<KubernetesDrawerContent
kind={kind}
toggleDrawer={toggleDrawer}
object={object}
renderObject={renderObject}
/>
</Drawer>
</>
);
};
interface KubernetesDrawerContentProps<T extends KubernetesDrawerable> {
toggleDrawer: (e: ChangeEvent<{}>, isOpen: boolean) => void;
object: T;
@@ -203,3 +144,61 @@ const KubernetesDrawerContent = <T extends KubernetesDrawerable>({
</>
);
};
interface KubernetesDrawerProps<T extends KubernetesDrawerable> {
object: T;
renderObject: (obj: T) => object;
buttonVariant?: 'h5' | 'subtitle2';
kind: string;
expanded?: boolean;
children?: React.ReactNode;
}
export const KubernetesDrawer = <T extends KubernetesDrawerable>({
object,
renderObject,
kind,
buttonVariant = 'subtitle2',
expanded = false,
children,
}: KubernetesDrawerProps<T>) => {
const [isOpen, setIsOpen] = useState(expanded);
const classes = useDrawerStyles();
const toggleDrawer = (e: ChangeEvent<{}>, newValue: boolean) => {
e.stopPropagation();
setIsOpen(newValue);
};
return (
<>
<PodDrawerButton
onClick={e => toggleDrawer(e, true)}
onFocus={event => event.stopPropagation()}
>
{children === undefined ? (
<Typography variant={buttonVariant}>
{object.metadata?.name ?? 'unknown object'}
</Typography>
) : (
children
)}
</PodDrawerButton>
<Drawer
classes={{
paper: classes.paper,
}}
anchor="right"
open={isOpen}
onClose={(e: any) => toggleDrawer(e, false)}
onClick={event => event.stopPropagation()}
>
<KubernetesDrawerContent
kind={kind}
toggleDrawer={toggleDrawer}
object={object}
renderObject={renderObject}
/>
</Drawer>
</>
);
};
@@ -29,46 +29,6 @@ import { V1Service } from '@kubernetes/client-node';
import { StructuredMetadataTable } from '@backstage/core';
import { ServiceDrawer } from './ServiceDrawer';
type ServicesAccordionsProps = {
deploymentResources: GroupedResponses;
};
export const ServicesAccordions = ({
deploymentResources,
}: ServicesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.services.map((service, i) => (
<Grid item key={i} xs>
<ServiceAccordion service={service} />
</Grid>
))}
</Grid>
);
};
type ServiceAccordionProps = {
service: V1Service;
};
const ServiceAccordion = ({ service }: ServiceAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ServiceSummary service={service} />
</AccordionSummary>
<AccordionDetails>
<ServiceCard service={service} />
</AccordionDetails>
</Accordion>
);
};
type ServiceSummaryProps = {
service: V1Service;
};
@@ -121,3 +81,43 @@ const ServiceCard = ({ service }: ServiceCardProps) => {
/>
);
};
type ServicesAccordionsProps = {
deploymentResources: GroupedResponses;
};
type ServiceAccordionProps = {
service: V1Service;
};
const ServiceAccordion = ({ service }: ServiceAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<ServiceSummary service={service} />
</AccordionSummary>
<AccordionDetails>
<ServiceCard service={service} />
</AccordionDetails>
</Accordion>
);
};
export const ServicesAccordions = ({
deploymentResources,
}: ServicesAccordionsProps) => {
return (
<Grid
container
direction="row"
justify="flex-start"
alignItems="flex-start"
>
{deploymentResources.services.map((service, i) => (
<Grid item key={i} xs>
<ServiceAccordion service={service} />
</Grid>
))}
</Grid>
);
};
+48 -89
View File
@@ -2413,7 +2413,7 @@
to-fast-properties "^2.0.0"
"@backstage/catalog-model@^0.2.0":
version "0.6.1"
version "0.7.0"
dependencies:
"@backstage/config" "^0.1.2"
"@types/json-schema" "^7.0.5"
@@ -2424,7 +2424,7 @@
yup "^0.29.3"
"@backstage/catalog-model@^0.3.0":
version "0.6.1"
version "0.7.0"
dependencies:
"@backstage/config" "^0.1.2"
"@types/json-schema" "^7.0.5"
@@ -2435,7 +2435,7 @@
yup "^0.29.3"
"@backstage/core@^0.3.0":
version "0.4.4"
version "0.5.0"
dependencies:
"@backstage/config" "^0.1.2"
"@backstage/core-api" "^0.2.8"
@@ -6440,11 +6440,6 @@
dependencies:
"@types/node" "*"
"@types/eslint-visitor-keys@^1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
"@types/eslint@*":
version "6.1.8"
resolved "https://registry.npmjs.org/@types/eslint/-/eslint-6.1.8.tgz#7e868f89bc1e520323d405940e49cb912ede5bba"
@@ -7478,91 +7473,62 @@
resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d"
integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==
"@typescript-eslint/eslint-plugin@^v3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f"
integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==
"@typescript-eslint/eslint-plugin@^v4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.0.tgz#92db8e7c357ed7d69632d6843ca70b71be3a721d"
integrity sha512-IJ5e2W7uFNfg4qh9eHkHRUCbgZ8VKtGwD07kannJvM5t/GU8P8+24NX8gi3Hf5jST5oWPY8kyV1s/WtfiZ4+Ww==
dependencies:
"@typescript-eslint/experimental-utils" "3.10.1"
"@typescript-eslint/experimental-utils" "4.14.0"
"@typescript-eslint/scope-manager" "4.14.0"
debug "^4.1.1"
functional-red-black-tree "^1.0.1"
lodash "^4.17.15"
regexpp "^3.0.0"
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
"@typescript-eslint/experimental-utils@4.14.0", "@typescript-eslint/experimental-utils@^4.0.1":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.0.tgz#5aa7b006736634f588a69ee343ca959cd09988df"
integrity sha512-6i6eAoiPlXMKRbXzvoQD5Yn9L7k9ezzGRvzC/x1V3650rUk3c3AOjQyGYyF9BDxQQDK2ElmKOZRD0CbtdkMzQQ==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/typescript-estree" "3.10.1"
"@typescript-eslint/scope-manager" "4.14.0"
"@typescript-eslint/types" "4.14.0"
"@typescript-eslint/typescript-estree" "4.14.0"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@^4.0.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.4.1.tgz#40613b9757fa0170de3e0043254dbb077cafac0c"
integrity sha512-Nt4EVlb1mqExW9cWhpV6pd1a3DkUbX9DeyYsdoeziKOpIJ04S2KMVDO+SEidsXRH/XHDpbzXykKcMTLdTXH6cQ==
"@typescript-eslint/parser@^v4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.0.tgz#62d4cd2079d5c06683e9bfb200c758f292c4dee7"
integrity sha512-sUDeuCjBU+ZF3Lzw0hphTyScmDDJ5QVkyE21pRoBo8iDl7WBtVFS+WDN3blY1CH3SBt7EmYCw6wfmJjF0l/uYg==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.4.1"
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/typescript-estree" "4.4.1"
eslint-scope "^5.0.0"
eslint-utils "^2.0.0"
"@typescript-eslint/parser@^v3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "3.10.1"
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/typescript-estree" "3.10.1"
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/scope-manager@4.4.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz#d19447e60db2ce9c425898d62fa03b2cce8ea3f9"
integrity sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ==
dependencies:
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/visitor-keys" "4.4.1"
"@typescript-eslint/types@3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
"@typescript-eslint/types@4.4.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.1.tgz#c507b35cf523bc7ba00aae5f75ee9b810cdabbc1"
integrity sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w==
"@typescript-eslint/typescript-estree@3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853"
integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==
dependencies:
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/visitor-keys" "3.10.1"
"@typescript-eslint/scope-manager" "4.14.0"
"@typescript-eslint/types" "4.14.0"
"@typescript-eslint/typescript-estree" "4.14.0"
debug "^4.1.1"
glob "^7.1.6"
is-glob "^4.0.1"
lodash "^4.17.15"
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@4.4.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz#598f6de488106c2587d47ca2462c60f6e2797cb8"
integrity sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g==
"@typescript-eslint/scope-manager@4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.14.0.tgz#55a4743095d684e1f7b7180c4bac2a0a3727f517"
integrity sha512-/J+LlRMdbPh4RdL4hfP1eCwHN5bAhFAGOTsvE6SxsrM/47XQiPSgF5MDgLyp/i9kbZV9Lx80DW0OpPkzL+uf8Q==
dependencies:
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/visitor-keys" "4.4.1"
"@typescript-eslint/types" "4.14.0"
"@typescript-eslint/visitor-keys" "4.14.0"
"@typescript-eslint/types@4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz#d8a8202d9b58831d6fd9cee2ba12f8a5a5dd44b6"
integrity sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==
"@typescript-eslint/typescript-estree@4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.0.tgz#4bcd67486e9acafc3d0c982b23a9ab8ac8911ed7"
integrity sha512-wRjZ5qLao+bvS2F7pX4qi2oLcOONIB+ru8RGBieDptq/SudYwshveORwCVU4/yMAd4GK7Fsf8Uq1tjV838erag==
dependencies:
"@typescript-eslint/types" "4.14.0"
"@typescript-eslint/visitor-keys" "4.14.0"
debug "^4.1.1"
globby "^11.0.1"
is-glob "^4.0.1"
@@ -7570,19 +7536,12 @@
semver "^7.3.2"
tsutils "^3.17.1"
"@typescript-eslint/visitor-keys@3.10.1":
version "3.10.1"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931"
integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==
"@typescript-eslint/visitor-keys@4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.0.tgz#b1090d9d2955b044b2ea2904a22496849acbdf54"
integrity sha512-MeHHzUyRI50DuiPgV9+LxcM52FCJFYjJiWHtXlbyC27b80mfOwKeiKI+MHOTEpcpfmoPFm/vvQS88bYIx6PZTA==
dependencies:
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/visitor-keys@4.4.1":
version "4.4.1"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz#1769dc7a9e2d7d2cfd3318b77ed8249187aed5c3"
integrity sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw==
dependencies:
"@typescript-eslint/types" "4.4.1"
"@typescript-eslint/types" "4.14.0"
eslint-visitor-keys "^2.0.0"
"@webassemblyjs/ast@1.9.0":