chore(lint): Updating to latest eslint with typescript and enabling some rules that we can. Also fixing issues with the project after update

This commit is contained in:
blam
2021-01-22 15:01:29 +01:00
parent ce526adb19
commit 5b9bcce28f
15 changed files with 471 additions and 461 deletions
+10 -3
View File
@@ -34,12 +34,12 @@ module.exports = {
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
lib: ['DOM', 'DOM.Iterable', 'ScriptHost', 'ES2020', 'ESNext.Promise'],
},
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: {
+10 -5
View File
@@ -26,13 +26,17 @@ module.exports = {
'plugin:monorepo/recommended',
],
parser: '@typescript-eslint/parser',
plugins: ['import'],
plugins: ['import', 'react'],
env: {
jest: true,
},
parserOptions: {
ecmaVersion: 2018,
ecmaFeatures: {
jsx: true,
},
sourceType: 'module',
lib: ['DOM', 'DOM.Iterable', 'ScriptHost', 'ES2020', 'ESNext.Promise'],
},
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',
},
},
{
@@ -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>
);
};
@@ -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>
);
};