Moving things into Home

This commit is contained in:
Stefan Ålund
2020-02-06 13:46:21 +01:00
parent f09d1065a0
commit 392fdf94e4
23 changed files with 235 additions and 484 deletions
+2
View File
@@ -18,7 +18,9 @@
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.3",
"@types/zen-observable": "^0.8.0",
"classnames": "^2.2.6",
"cross-env": "^7.0.0",
"rc-progress": "^2.5.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core';
import { Circle } from 'rc-progress';
import { COLORS, V1 } from 'core/app/Themes';
import { COLORS } from '@backstage/core';
const styles = theme => ({
root: {
@@ -43,7 +43,7 @@ class CircleProgress extends Component {
static getProgressColor(value, inverse, max /* , classes */) {
if (isNaN(value)) {
return V1.palette.textVerySubtle;
return 'grey';
}
max = max ? max : CircleProgress.defaultProps.max;
@@ -70,10 +70,17 @@ class CircleProgress extends Component {
percent={asPercentage}
strokeWidth="12"
trailWidth="12"
strokeColor={CircleProgress.getProgressColor(asActual, inverse, max, classes)}
strokeColor={CircleProgress.getProgressColor(
asActual,
inverse,
max,
classes,
)}
className={classes.circle}
/>
<div className={classes.overlay}>{isNaN(value) ? 'N/A' : `${asActual}${unit}`}</div>
<div className={classes.overlay}>
{isNaN(value) ? 'N/A' : `${asActual}${unit}`}
</div>
</div>
);
}
@@ -3,8 +3,7 @@ import classNames from 'classnames';
import { makeStyles, Theme } from '@material-ui/core/styles';
import ChevronLeftIcon from '@material-ui/icons/ChevronLeft';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import { IconButton } from '.';
import { Grid } from '@material-ui/core';
import { Grid, IconButton } from '@material-ui/core';
// Generated with https://larsenwork.com/easing-gradients/
const fadeGradient = `
@@ -79,8 +78,12 @@ const useStyles = makeStyles<Theme, Props>(theme => ({
}));
// Returns scroll distance from left and right
function useScrollDistance(ref: React.MutableRefObject<HTMLElement | undefined>): [number, number] {
const [[scrollLeft, scrollRight], setScroll] = React.useState<[number, number]>([0, 0]);
function useScrollDistance(
ref: React.MutableRefObject<HTMLElement | undefined>,
): [number, number] {
const [[scrollLeft, scrollRight], setScroll] = React.useState<
[number, number]
>([0, 0]);
React.useLayoutEffect(() => {
const el = ref.current;
@@ -110,7 +113,11 @@ function useScrollDistance(ref: React.MutableRefObject<HTMLElement | undefined>)
// Used to animate scrolling. Returns a single setScrollTarger function, when called with e.g. 200,
// the element pointer to by the ref will be scrolled 200px forwards over time.
function useSmoothScroll(ref: React.MutableRefObject<HTMLElement | undefined>, speed: number, minDistance: number) {
function useSmoothScroll(
ref: React.MutableRefObject<HTMLElement | undefined>,
speed: number,
minDistance: number,
) {
const [scrollTarget, setScrollTarget] = React.useState<number>(0);
React.useLayoutEffect(() => {
@@ -145,7 +152,13 @@ function useSmoothScroll(ref: React.MutableRefObject<HTMLElement | undefined>, s
}
const HorizontalScrollGrid: FC<Props> = props => {
const { scrollStep = 100, scrollSpeed = 50, minScrollDistance = 5, children, ...otherProps } = props;
const {
scrollStep = 100,
scrollSpeed = 50,
minScrollDistance = 5,
children,
...otherProps
} = props;
const classes = useStyles(props);
const ref = React.useRef<HTMLElement>();
@@ -162,7 +175,13 @@ const HorizontalScrollGrid: FC<Props> = props => {
return (
<div {...otherProps} className={classes.root}>
<Grid container direction="row" wrap="nowrap" className={classes.container} ref={ref as any}>
<Grid
container
direction="row"
wrap="nowrap"
className={classes.container}
ref={ref as any}
>
{children}
</Grid>
<div
@@ -0,0 +1,60 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core';
import { InfoCard, CircleProgress } from '@backstage/core';
const styles = {
root: {
height: '100%',
width: 250,
},
};
class ProgressCard extends Component {
static propTypes = {
classes: PropTypes.object.isRequired,
title: PropTypes.string.isRequired,
subheader: PropTypes.string,
progress: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
.isRequired,
deepLink: PropTypes.oneOfType([
PropTypes.string,
PropTypes.shape({
title: PropTypes.string.isRequired,
link: PropTypes.string.isRequired,
}),
]),
gacontext: PropTypes.string,
};
render() {
const {
title,
subheader,
progress,
deepLink,
classes,
variant,
} = this.props;
const link =
deepLink &&
(typeof deepLink === 'string'
? { title: 'View more', link: deepLink }
: deepLink);
return (
<div className={classes.root}>
<InfoCard
title={title}
subheader={subheader}
deepLink={link}
variant={variant}
>
<CircleProgress value={progress} />
</InfoCard>
</div>
);
}
}
export default withStyles(styles)(ProgressCard);
@@ -0,0 +1,31 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInThemedTestApp } from 'testUtils';
import ProgressCard from './ProgressCard';
const minProps = { title: 'Tingle upgrade', progress: 0.12 };
describe('<ProgressCard />', () => {
it('renders without exploding', () => {
const { getByText } = render(wrapInThemedTestApp(<ProgressCard {...minProps} />));
expect(getByText(/Tingle.*/)).toBeInTheDocument();
});
it('renders progress and title', () => {
const { getByText } = render(wrapInThemedTestApp(<ProgressCard {...minProps} />));
expect(getByText(/Tingle.*/)).toBeInTheDocument();
expect(getByText(/12%.*/)).toBeInTheDocument();
});
it('does not render deepLink', () => {
const { queryByText } = render(wrapInThemedTestApp(<ProgressCard {...minProps} />));
expect(queryByText('View more')).not.toBeInTheDocument();
});
it('handles invalid numbers', () => {
const badProps = { title: 'Tingle upgrade', progress: 'hejjo' };
const { getByText } = render(wrapInThemedTestApp(<ProgressCard {...badProps} />));
expect(getByText(/N\/A.*/)).toBeInTheDocument();
});
});
+5
View File
@@ -10,3 +10,8 @@ export { default as HeaderLabel } from '../src/layout/HeaderLabel';
export { default as InfoCard } from '../src/layout/InfoCard';
export { default as ErrorBoundary } from '../src/layout/ErrorBoundary';
export { default as BackstageTheme } from '../src/theme/BackstageTheme';
export { COLORS } from '../src/theme/BackstageTheme';
export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid';
export { default as ProgressCard } from './components/ProgressCard';
export { default as CircleProgress } from './components/CircleProgress';
export { default as Progress } from './components/Progress';
@@ -1,20 +1,29 @@
import { EntityLink, InfoCard } from '@backstage/core';
import { Typography } from '@material-ui/core';
import React, { FC } from 'react';
import { EntityLink, InfoCard } from '@backstage/core';
import SquadTechHealth from './SquadTechHealth';
import { Grid, Typography } from '@material-ui/core';
const HomePage: FC<{}> = () => {
return (
<InfoCard title="Home Page">
<Typography variant="body1">Welcome to Backstage!</Typography>
<div>
<EntityLink kind="service" id="backstage-backend">
Backstage Backend
</EntityLink>
<EntityLink uri="entity:service:backstage-lb" subPath="ci-cd">
Backstage LB CI/CD
</EntityLink>
</div>
</InfoCard>
<Grid container direction="column" spacing={6}>
<Grid item>
<SquadTechHealth />
</Grid>
<Grid item>
<InfoCard title="Home">
<Typography variant="body1">Welcome to Backstage!</Typography>
<div>
<EntityLink kind="service" id="backstage-backend">
Backstage Backend
</EntityLink>
<EntityLink uri="entity:service:backstage-lb" subPath="ci-cd">
Backstage LB CI/CD
</EntityLink>
</div>
</InfoCard>
</Grid>
</Grid>
);
};
@@ -0,0 +1,26 @@
import SquadTechHealth from './SquadTechHealth';
import { buildComponentInApp } from 'testUtils';
const minProps = {
user: {
googleCloudPlatformProjects: [],
squads: [{ id: 'tools', googleCloudPlatformProjects: [], services: [] }],
},
squads: ['tools'],
insights: {
testCertified: 0.5,
},
};
describe('<SquadTechHealth/>', () => {
it('renders without exploding', () => {
const rendered = buildComponentInApp(SquadTechHealth)
.withTheme()
.render(minProps);
expect(rendered.getByText('Test Certified')).toBeInTheDocument();
expect(rendered.getByText('50%')).toBeInTheDocument();
expect(rendered.getByText('Cloud Cost')).toBeInTheDocument();
});
});
@@ -0,0 +1,36 @@
import React, { FC } from 'react';
import { Grid, Typography } from '@material-ui/core';
import { HorizontalScrollGrid, ProgressCard } from '@backstage/core';
const SquadTechHealth: FC<{}> = () => {
return (
<>
<Typography variant="h3">Team Metrics</Typography>
<HorizontalScrollGrid scrollStep={400} scrollSpeed={100}>
<Grid item>
<ProgressCard
title="Test Certified"
progress={0.23}
deepLink={{
link: '/some-url',
title: 'About Test Certs',
}}
/>
</Grid>
<Grid item>
<ProgressCard
title="k8s Migration"
progress={0.78}
deepLink={{
link: '/some-url',
title: 'About k8s',
}}
/>
</Grid>
</HorizontalScrollGrid>
</>
);
};
export default SquadTechHealth;
@@ -1,31 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Link from 'shared/components/Link';
import { Divider, ListItemText } from '@material-ui/core';
import { ListItem, ListItemIcon } from '@material-ui/core';
import ArrowIcon from '@material-ui/icons/ArrowForward';
export default class BottomLink extends Component {
static propTypes = {
link: PropTypes.string,
title: PropTypes.string,
onClick: PropTypes.func,
};
render() {
const { link, title, onClick } = this.props;
return (
<div>
<Divider />
<Link to={link} onClick={onClick} highlight="none">
<ListItem>
<ListItemIcon>
<ArrowIcon />
</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
</Link>
</div>
);
}
}
@@ -1,17 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from 'testUtils';
import BottomLink from './BottomLink';
const minProps = {
title: 'A deepLink title',
link: '/mocked',
};
describe('<BottomLink />', () => {
it('renders without exploding', () => {
const rendered = render(wrapInTestApp(<BottomLink {...minProps} />));
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
});
@@ -1,169 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Card, CardActions, CardContent, CardHeader, Divider, withStyles } from '@material-ui/core';
import ErrorBoundary from 'shared/components/ErrorBoundary';
import BottomLink from './BottomLink';
import textContent from 'react-addons-text-content';
const BoldHeader = withStyles({ title: { fontWeight: '700' } })(CardHeader);
const CardActionsTopRight = withStyles({
root: {
display: 'inline-block',
paddingRight: '16px',
paddingTop: '16px',
float: 'right',
},
})(CardActions);
const VARIANT_STYLES = {
card: {
flex: {
display: 'flex',
flexDirection: 'column',
},
widget: {
height: 430,
},
fullHeight: {
height: '100%',
},
height100: {
display: 'flex',
flexDirection: 'column',
height: 'calc(100% - 10px)', // for pages without content header
marginBottom: '10px',
},
contentheader: {
height: 'calc(100% - 40px)', // for pages with content header
},
contentheadertabs: {
height: 'calc(100% - 97px)', // for pages with content header and tabs (Tingle)
},
noShrink: {
flexShrink: 0,
},
minheight300: {
minHeight: 300,
overflow: 'initial',
},
},
cardContent: {
widget: {
overflowY: 'auto',
height: 332,
width: '100%',
},
fullHeight: {
height: 'calc(100% - 50px)',
},
height100: {
height: 'calc(100% - 50px)',
},
contentRow: {
display: 'flex',
flexDirection: 'row',
},
},
};
/**
* InfoCard is used to display a paper-styled block on the screen, similar to a panel.
*
* You can custom style an InfoCard with the 'style' (outer container) and 'cardStyle' (inner container)
* styles.
*
* The InfoCard serves as an error boundary. As a result, if you provide a 'slackChannel' property this
* specifies the channel to display in the error component that is displayed if an error occurs
* in any descendent components.
*
* By default the InfoCard has no custom layout of its children, but is treated as a block element. A
* couple common variants are provided and can be specified via the variant property:
*
* Display the card full height suitable for DataGrid:
*
* <InfoCard variant="height100">...</InfoCard>
*
* Variants can be combined in a whitespace delimited list like so:
*
* <InfoCard variant="noShrink">...</InfoCard>
*/
class InfoCard extends Component {
static propTypes = {
title: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
subheader: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
divider: PropTypes.bool,
deepLink: PropTypes.object,
slackChannel: PropTypes.string,
variant: PropTypes.string,
};
render() {
const {
title,
subheader,
divider,
deepLink,
children,
actions,
actionsTopRight,
headerStyle,
headerProps,
classes,
slackChannel,
variant,
} = this.props;
if (this.props.style) {
console.warn('InfoCard: using `style` property directly, consider migrating your style to variant in InfoCard');
}
if (this.props.cardStyle) {
console.warn(
'InfoCard: using `cardStyle` property directly, consider migrating your style to variant in InfoCard',
);
}
/**
* If variant is specified, we build up styles for that particular variant for both
* the Card and the CardContent (since these need to be synced)
*/
let calculatedStyle = {};
let calculatedCardStyle = {};
if (variant) {
let variants = variant.split(/[\s]+/g);
variants.forEach(name => {
calculatedStyle = { ...calculatedStyle, ...VARIANT_STYLES.card[name] };
calculatedCardStyle = { ...calculatedCardStyle, ...VARIANT_STYLES.cardContent[name] };
});
}
// Apply the passed styles on top
const computedStyle = { ...calculatedStyle, ...this.props.style };
const computedCardStyle = { ...calculatedCardStyle, ...this.props.cardStyle };
return (
<Card style={computedStyle} classes={classes} gacontext={textContent(title)}>
<ErrorBoundary slackChannel={slackChannel}>
{title && (
<BoldHeader
title={title}
subheader={subheader}
style={{ display: 'inline-block', ...headerStyle }}
{...headerProps}
/>
)}
{actionsTopRight && <CardActionsTopRight>{actionsTopRight}</CardActionsTopRight>}
{divider && <Divider />}
<CardContent className={this.props.cardClassName} style={computedCardStyle}>
{children}
</CardContent>
{actions && <CardActions className={this.props.actionsClassName}>{actions}</CardActions>}
{deepLink && <BottomLink {...deepLink} />}
</ErrorBoundary>
</Card>
);
}
}
export default InfoCard;
@@ -1,24 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from 'testUtils';
import InfoCard from './InfoCard';
const minProps = {
title: 'Some title',
deepLink: {
title: 'A deepLink title',
link: '/mocked',
},
};
describe('<InfoCard />', () => {
it('renders without exploding', () => {
const rendered = render(wrapInTestApp(<InfoCard {...minProps} />));
expect(rendered.getByText('Some title')).toBeInTheDocument();
});
it('renders a deepLink when prop is set', () => {
const rendered = render(wrapInTestApp(<InfoCard {...minProps} />));
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
});
@@ -1 +0,0 @@
export { default } from './InfoCard';
@@ -1,6 +0,0 @@
# Sidebar Components
This is a collection of components that appear inside sidebars across the app.
In particular, the `SidebarList` hierarchy defines some common variants of lists, that are actually
themed MUI MenuList/List components.
@@ -1,114 +0,0 @@
import React, { FC } from 'react';
import {
createMuiTheme,
makeStyles,
MuiThemeProvider,
MenuList,
MenuItem,
ListItemText,
ListSubheader,
ListItemIcon,
ListItemSecondaryAction,
} from '@material-ui/core';
import { useLocation } from 'react-router-dom';
import { Link } from 'shared/components';
const themes = {
large: createMuiTheme({
overrides: {
MuiList: { root: { outline: 'none' } },
MuiMenuItem: { root: { lineHeight: 1 } },
MuiListItemIcon: { root: { minWidth: 36 } },
MuiListItemText: { primary: { fontWeight: 600 } },
MuiListSubheader: { root: { lineHeight: 'unset', textTransform: 'uppercase', fontWeight: 'unset' } },
MuiListItemSecondaryAction: { root: { pointerEvents: 'none' } },
},
}),
medium: createMuiTheme({
overrides: {
MuiList: { root: { outline: 'none' } },
MuiMenuItem: { root: { lineHeight: 1 }, dense: { paddingTop: 0, paddingBottom: 0 } },
MuiListItemIcon: { root: { minWidth: 36 } },
MuiListItemText: { primary: { fontWeight: 600 } },
MuiListSubheader: { root: { lineHeight: 'unset', textTransform: 'uppercase', fontWeight: 'unset' } },
MuiListItemSecondaryAction: { root: { pointerEvents: 'none' } },
},
}),
small: createMuiTheme({
overrides: {
MuiList: { root: { outline: 'none' } },
MuiMenuItem: { root: { lineHeight: 1 }, dense: { paddingTop: 0, paddingBottom: 0 } },
MuiListItemIcon: { root: { minWidth: 36 } },
MuiListItemText: { primary: {} },
MuiListSubheader: { root: { lineHeight: 'unset', textTransform: 'uppercase', fontWeight: 'unset' } },
MuiListItemSecondaryAction: { root: { pointerEvents: 'none' } },
},
}),
};
const useStyles = makeStyles(() => ({
active: {
'&::before': {
content: "''",
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: 4,
backgroundColor: 'green',
},
backgroundColor: 'rgba(0, 0, 0, 0.1)',
},
}));
// Hook that decides if the current page location matches the given link
function useLocationMatch(linkTo?: string) {
const { pathname } = useLocation();
if (!linkTo) {
return false;
} else if (linkTo === '/') {
return pathname === '' || pathname === '/';
}
const l = linkTo.replace(/\/*$/, '');
return pathname === l || pathname.startsWith(`${l}/`);
}
export type SidebarListProps = {
variant: 'large' | 'medium' | 'small';
subheader?: string;
};
export type SidebarListItemProps = {
linkTo?: string;
onClick?: () => any;
icon?: React.ReactElement;
secondary?: React.ReactElement;
children: string;
};
export const SidebarList: FC<SidebarListProps> = ({ variant, subheader, children }) => {
return (
<MuiThemeProvider theme={themes[variant]}>
<MenuList dense subheader={subheader ? <ListSubheader>{subheader}</ListSubheader> : undefined}>
{children}
</MenuList>
</MuiThemeProvider>
);
};
export const SidebarListItem: FC<SidebarListItemProps> = ({ linkTo, onClick, icon, secondary, children }) => {
const classes = useStyles();
const isActive = useLocationMatch(linkTo);
return (
<Link to={linkTo} onClick={() => onClick && onClick()}>
<MenuItem classes={{ root: isActive ? classes.active : undefined }}>
{icon ? <ListItemIcon>{icon}</ListItemIcon> : null}
<ListItemText primary={children} />
{secondary ? <ListItemSecondaryAction>{secondary}</ListItemSecondaryAction> : null}
</MenuItem>
</Link>
);
};
@@ -1,93 +0,0 @@
import React, { FC } from 'react';
import { Divider as MuiDivider, Typography, IconButton, makeStyles } from '@material-ui/core';
import BackIcon from '@material-ui/icons/ChevronLeftOutlined';
import { useColumnStackControls } from 'shared/components/ColumnStack';
const useStyles = makeStyles(theme => ({
root: {
display: 'flex',
flexDirection: 'column',
minHeight: '100%',
},
header: {
padding: theme.spacing(1, 2, 1, 2),
},
headerTitle: {
display: 'flex',
alignItems: 'center',
},
spacer: {
flex: `0 0 ${theme.spacing(2)}px`,
},
flex: {
flex: 1,
},
backButton: {
margin: theme.spacing(0, 1, 0, -1),
},
secondaryText: {
color: theme.palette.grey[500],
fontSize: '85%',
pointerEvents: 'none',
userSelect: 'none',
},
}));
export type ColumnProps = {
width: number;
};
export const BackButton: FC<{}> = () => {
const columnStack = useColumnStackControls();
const classes = useStyles();
return (
<IconButton
onClick={() => columnStack.pop()}
size="small"
className={classes.backButton}
data-testid="sidebar-back-button"
>
<BackIcon />
</IconButton>
);
};
export const Column: FC<ColumnProps> = ({ width, children }) => {
const classes = useStyles();
return (
<div className={classes.root} style={{ width }}>
{children}
</div>
);
};
export const Header: FC<{}> = ({ children }) => {
const classes = useStyles();
return <div className={classes.header}>{children}</div>;
};
export const HeaderTitle: FC<{}> = ({ children }) => {
const classes = useStyles();
return (
<Typography variant="h6" className={classes.headerTitle}>
{children}
</Typography>
);
};
export const Spacer: FC<{}> = () => {
const classes = useStyles();
return <div className={classes.spacer} />;
};
export const Flex: FC<{}> = () => {
const classes = useStyles();
return <div className={classes.flex} />;
};
export const Divider: FC<{}> = () => <MuiDivider />;
export const SecondaryText: FC<{}> = ({ children }) => {
const classes = useStyles();
return <div className={classes.secondaryText}>{children}</div>;
};
@@ -1,2 +0,0 @@
export * from './common';
export * from './SidebarList';
+16 -3
View File
@@ -3913,7 +3913,7 @@ babel-preset-react-app@^9.1.0:
babel-plugin-macros "2.8.0"
babel-plugin-transform-react-remove-prop-types "0.4.24"
babel-runtime@^6.23.0, babel-runtime@^6.26.0:
babel-runtime@6.x, babel-runtime@^6.23.0, babel-runtime@^6.26.0:
version "6.26.0"
resolved "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4=
@@ -4563,6 +4563,11 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.6:
version "2.2.6"
resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha1-Q5Nb/90pHzJtrQogUwmzjQD2UM4=
clean-css@4.2.x:
version "4.2.3"
resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
@@ -13185,7 +13190,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@^15.5.4, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -13385,6 +13390,14 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
rc-progress@^2.5.2:
version "2.5.2"
resolved "https://artifactory.spotify.net/artifactory/api/npm/virtual-npm/rc-progress/-/rc-progress-2.5.2.tgz#ab01ba4e5d2fa36fc9f6f058b10b720e7315560c"
integrity sha1-qwG6Tl0vo2/J9vBYsQtyDnMVVgw=
dependencies:
babel-runtime "6.x"
prop-types "^15.5.8"
rc@^1.0.1, rc@^1.1.6, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -15724,7 +15737,7 @@ ts-protoc-gen@^0.12.0:
dependencies:
google-protobuf "^3.6.1"
tslib@^1.8.1, tslib@^1.9.0:
tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==