Merge pull request #27699 from kalleericson/template-card-meta-data
Feat: Additional test coverage and component structure for TemplateCard
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Added test coverage selectors to TemplateCard and its sub-components
|
||||
@@ -19,7 +19,7 @@ import { Link } from '@backstage/core-components';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
interface CardLinkProps {
|
||||
export interface CardLinkProps {
|
||||
icon: IconComponent;
|
||||
text: string;
|
||||
url: string;
|
||||
|
||||
@@ -15,67 +15,26 @@
|
||||
*/
|
||||
|
||||
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
|
||||
import { MarkdownContent, UserIcon } from '@backstage/core-components';
|
||||
import {
|
||||
IconComponent,
|
||||
useAnalytics,
|
||||
useApp,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
EntityRefLinks,
|
||||
getEntityRelations,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { IconComponent, useAnalytics } from '@backstage/core-plugin-api';
|
||||
import { getEntityRelations } from '@backstage/plugin-catalog-react';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
import React, { useCallback } from 'react';
|
||||
import { CardHeader } from './CardHeader';
|
||||
import { CardLink } from './CardLink';
|
||||
import { usePermission } from '@backstage/plugin-permission-react';
|
||||
import { taskCreatePermission } from '@backstage/plugin-scaffolder-common/alpha';
|
||||
import { TemplateCardContent } from './TemplateCardContent';
|
||||
import { TemplateCardTags } from './TemplateCardTags';
|
||||
import { TemplateCardLinks } from './TemplateCardLinks';
|
||||
import { TemplateCardActions } from './TemplateCardActions';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
box: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
'-webkit-line-clamp': 10,
|
||||
'-webkit-box-orient': 'vertical',
|
||||
},
|
||||
markdown: {
|
||||
/** to make the styles for React Markdown not leak into the description */
|
||||
'& :first-child': {
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
label: {
|
||||
color: theme.palette.text.secondary,
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 0.5,
|
||||
lineHeight: 1,
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
footer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
ownedBy: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
color: theme.palette.link,
|
||||
},
|
||||
const useStyles = makeStyles<Theme>(() => ({
|
||||
actionContainer: { padding: '16px', flex: 1, alignItems: 'flex-end' },
|
||||
}));
|
||||
|
||||
/**
|
||||
@@ -89,7 +48,6 @@ export interface TemplateCardProps {
|
||||
text: string;
|
||||
url: string;
|
||||
}[];
|
||||
|
||||
onSelected?: (template: TemplateEntityV1beta3) => void;
|
||||
}
|
||||
|
||||
@@ -98,16 +56,13 @@ export interface TemplateCardProps {
|
||||
* @alpha
|
||||
*/
|
||||
export const TemplateCard = (props: TemplateCardProps) => {
|
||||
const { onSelected, template } = props;
|
||||
const { additionalLinks, onSelected, template } = props;
|
||||
const styles = useStyles();
|
||||
const analytics = useAnalytics();
|
||||
const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY);
|
||||
const app = useApp();
|
||||
const iconResolver = (key?: string): IconComponent =>
|
||||
key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon;
|
||||
const hasTags = !!template.metadata.tags?.length;
|
||||
const hasLinks =
|
||||
!!props.additionalLinks?.length || !!template.metadata.links?.length;
|
||||
!!additionalLinks?.length || !!template.metadata.links?.length;
|
||||
const displayDefaultDivider = !hasTags && !hasLinks;
|
||||
|
||||
const { allowed: canCreateTask } = usePermission({
|
||||
@@ -120,98 +75,33 @@ export const TemplateCard = (props: TemplateCardProps) => {
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader template={template} />
|
||||
<CardHeader template={template} data-testid="template-card-header" />
|
||||
<CardContent>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Box className={styles.box}>
|
||||
<MarkdownContent
|
||||
className={styles.markdown}
|
||||
content={template.metadata.description ?? 'No description'}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid container spacing={2} data-testid="template-card-content">
|
||||
<TemplateCardContent template={template} />
|
||||
{displayDefaultDivider && (
|
||||
<Grid item xs={12}>
|
||||
<Divider data-testid="template-card-separator" />
|
||||
</Grid>
|
||||
)}
|
||||
{hasTags && (
|
||||
<>
|
||||
<Grid item xs={12}>
|
||||
<Divider data-testid="template-card-separator--tags" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
{template.metadata.tags?.map(tag => (
|
||||
<Grid key={`grid-${tag}`} item>
|
||||
<Chip
|
||||
style={{ margin: 0 }}
|
||||
size="small"
|
||||
label={tag}
|
||||
key={tag}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{hasTags && <TemplateCardTags template={template} />}
|
||||
{hasLinks && (
|
||||
<>
|
||||
<Grid item xs={12}>
|
||||
<Divider data-testid="template-card-separator--links" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
{props.additionalLinks?.map(({ icon, text, url }, index) => (
|
||||
<Grid className={styles.linkText} item xs={6} key={index}>
|
||||
<CardLink icon={icon} text={text} url={url} />
|
||||
</Grid>
|
||||
))}
|
||||
{template.metadata.links?.map(
|
||||
({ url, icon, title }, index) => (
|
||||
<Grid className={styles.linkText} item xs={6} key={index}>
|
||||
<CardLink
|
||||
icon={iconResolver(icon)}
|
||||
text={title || url}
|
||||
url={url}
|
||||
/>
|
||||
</Grid>
|
||||
),
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
<TemplateCardLinks
|
||||
template={template}
|
||||
additionalLinks={additionalLinks}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</CardContent>
|
||||
<CardActions style={{ padding: '16px', flex: 1, alignItems: 'flex-end' }}>
|
||||
<div className={styles.footer}>
|
||||
<div className={styles.ownedBy}>
|
||||
{ownedByRelations.length > 0 && (
|
||||
<>
|
||||
<UserIcon fontSize="small" />
|
||||
<EntityRefLinks
|
||||
style={{ marginLeft: '8px' }}
|
||||
entityRefs={ownedByRelations}
|
||||
defaultKind="Group"
|
||||
hideIcons
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{canCreateTask ? (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
onClick={handleChoose}
|
||||
>
|
||||
Choose
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
<CardActions
|
||||
className={styles.actionContainer}
|
||||
data-testid="template-card-actions"
|
||||
>
|
||||
<TemplateCardActions
|
||||
canCreateTask={canCreateTask}
|
||||
handleChoose={handleChoose}
|
||||
ownedByRelations={ownedByRelations}
|
||||
/>
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2022 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 { UserIcon } from '@backstage/core-components';
|
||||
import { EntityRefLinks } from '@backstage/plugin-catalog-react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles<Theme>(theme => ({
|
||||
footer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
ownedBy: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
color: theme.palette.link,
|
||||
},
|
||||
actionContainer: { padding: '16px', flex: 1, alignItems: 'flex-end' },
|
||||
}));
|
||||
|
||||
/**
|
||||
* The Props for the {@link TemplateCardActions} component
|
||||
* @alpha
|
||||
*/
|
||||
export interface TemplateCardActionsProps {
|
||||
ownedByRelations: any;
|
||||
canCreateTask: boolean;
|
||||
handleChoose: () => void;
|
||||
}
|
||||
export const TemplateCardActions = ({
|
||||
canCreateTask,
|
||||
handleChoose,
|
||||
ownedByRelations,
|
||||
}: TemplateCardActionsProps) => {
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.footer} data-testid="template-card-actions--footer">
|
||||
<div
|
||||
className={styles.ownedBy}
|
||||
data-testid="template-card-actions--ownedby"
|
||||
>
|
||||
{ownedByRelations.length > 0 && (
|
||||
<>
|
||||
<UserIcon fontSize="small" />
|
||||
<EntityRefLinks
|
||||
style={{ marginLeft: '8px' }}
|
||||
entityRefs={ownedByRelations}
|
||||
defaultKind="Group"
|
||||
hideIcons
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{canCreateTask ? (
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
data-testid="template-card-actions--create"
|
||||
onClick={handleChoose}
|
||||
>
|
||||
Choose
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2022 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 { MarkdownContent } from '@backstage/core-components';
|
||||
import type { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Box from '@material-ui/core/Box';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import React from 'react';
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
box: {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
display: '-webkit-box',
|
||||
'-webkit-line-clamp': 10,
|
||||
'-webkit-box-orient': 'vertical',
|
||||
},
|
||||
markdown: {
|
||||
/** to make the styles for React Markdown not leak into the description */
|
||||
'& :first-child': {
|
||||
margin: 0,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* The Props for the {@link TemplateCardContent} component
|
||||
* @alpha
|
||||
*/
|
||||
export interface TemplateCardContentProps {
|
||||
template: TemplateEntityV1beta3;
|
||||
}
|
||||
export const TemplateCardContent = ({ template }: TemplateCardContentProps) => {
|
||||
const styles = useStyles();
|
||||
return (
|
||||
<Grid item xs={12} data-testid="template-card-content-grid">
|
||||
<Box className={styles.box} data-testid="template-card-content-container">
|
||||
<MarkdownContent
|
||||
className={styles.markdown}
|
||||
content={template.metadata.description ?? 'No description'}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2022 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 { IconComponent, useApp } from '@backstage/core-plugin-api';
|
||||
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import { makeStyles, Theme } from '@material-ui/core/styles';
|
||||
import LanguageIcon from '@material-ui/icons/Language';
|
||||
import React from 'react';
|
||||
import { CardLink } from './CardLink';
|
||||
|
||||
const useStyles = makeStyles<Theme>({});
|
||||
|
||||
/**
|
||||
* The Props for the {@link TemplateCardLinks} component
|
||||
* @alpha
|
||||
*/
|
||||
export interface TemplateCardLinksProps {
|
||||
template: TemplateEntityV1beta3;
|
||||
additionalLinks?: {
|
||||
icon: IconComponent;
|
||||
text: string;
|
||||
url: string;
|
||||
}[];
|
||||
}
|
||||
export const TemplateCardLinks = ({
|
||||
template,
|
||||
additionalLinks,
|
||||
}: TemplateCardLinksProps) => {
|
||||
const styles = useStyles();
|
||||
const app = useApp();
|
||||
const iconResolver = (key?: string): IconComponent =>
|
||||
key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon;
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12}>
|
||||
<Divider data-testid="template-card-separator--links" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2} data-testid="template-card-links">
|
||||
{additionalLinks?.map(({ icon, text, url }, index) => (
|
||||
<Grid
|
||||
className={styles.linkText}
|
||||
item
|
||||
xs={6}
|
||||
key={index}
|
||||
data-testid="template-card-links--item"
|
||||
>
|
||||
<CardLink icon={icon} text={text} url={url} />
|
||||
</Grid>
|
||||
))}
|
||||
{template.metadata.links?.map(({ url, icon, title }, index) => (
|
||||
<Grid
|
||||
className={styles.linkText}
|
||||
item
|
||||
xs={6}
|
||||
key={index}
|
||||
data-testid="template-card-links--metalink"
|
||||
>
|
||||
<CardLink
|
||||
icon={iconResolver(icon)}
|
||||
text={title || url}
|
||||
url={url}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2022 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 type { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* The Props for the {@link TemplateCardTags} component
|
||||
* @alpha
|
||||
*/
|
||||
export interface TemplateCardTagsProps {
|
||||
template: TemplateEntityV1beta3;
|
||||
}
|
||||
export const TemplateCardTags = ({ template }: TemplateCardTagsProps) => (
|
||||
<>
|
||||
<Grid item xs={12}>
|
||||
<Divider data-testid="template-card-separator--tags" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2} data-testid="template-card-tags">
|
||||
{template.metadata.tags?.map(tag => (
|
||||
<Grid
|
||||
key={`grid-${tag}`}
|
||||
item
|
||||
data-testid={`template-card-tag-item-${tag}`}
|
||||
>
|
||||
<Chip
|
||||
style={{ margin: 0 }}
|
||||
size="small"
|
||||
data-testid={`template-card-tag-chip-${tag}`}
|
||||
label={tag}
|
||||
key={tag}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
Reference in New Issue
Block a user