Skip to content

Commit

Permalink
Add ThreatsDataView
Browse files Browse the repository at this point in the history
  • Loading branch information
nateweller committed Oct 29, 2024
1 parent 0802197 commit f27d02f
Show file tree
Hide file tree
Showing 29 changed files with 1,755 additions and 80 deletions.
366 changes: 360 additions & 6 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add ThreatsDataView component

This file was deleted.

39 changes: 39 additions & 0 deletions projects/js-packages/components/components/badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import clsx from 'clsx';
import React from 'react';
import styles from './style.module.scss';

type BadgeProps = {
children?: React.ReactNode;
className?: string;
variant?: 'success' | 'warning' | 'danger';
[ key: string ]: unknown;
};

/**
* Badge component
*
* @param {object} props - The component properties.
* @param {string} props.variant - The badge variant (i.e. 'success', 'warning', 'danger').
* @param {JSX.Element} props.children - Badge text or content.
* @param {string} props.className - Additional class name to pass to the Badge component.
*
* @return {React.ReactElement} The `Badge` component.
*/
const Badge: React.FC< BadgeProps > = ( { children, className, variant = 'info', ...props } ) => {
const classes = clsx(
styles.badge,
{
[ styles[ 'is-success' ] ]: variant === 'success',
[ styles[ 'is-warning' ] ]: variant === 'warning',
[ styles[ 'is-danger' ] ]: variant === 'danger',
},
className
);
return (
<span className={ classes } { ...props }>
{ children }
</span>
);
};

export default Badge;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Badge from '../index';

export default {
title: 'JS Packages/Components/Badge',
component: Badge,
argTypes: {
type: {
control: {
type: 'select',
},
options: [ 'info', 'danger', 'warning', 'success' ],
},
},
};

const Template = args => <Badge { ...args } />;

export const _default = Template.bind( {} );
_default.args = {
type: 'info',
children: 'Hello World',
};
25 changes: 25 additions & 0 deletions projects/js-packages/components/components/badge/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.badge {
display: inline-block;
border-radius: 4px;
background-color: var(--jp-gray-0);
color: var(--jp-gray-80);
padding: 4px 8px;
font-size: 13px;
font-weight: 400;
line-height: 16px;

&.is-success {
background-color: var(--jp-green-5);
color: var(--jp-green-50);
}

&.is-warning {
background-color: var(--jp-yellow-5);
color: var(--jp-yellow-60);
}

&.is-danger {
background-color: var(--jp-red-5);
color: var(--jp-red-70);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@ const IconTooltip: React.FC< IconTooltipProps > = ( {
children,
popoverAnchorStyle = 'icon',
forceShow = false,
hoverShow = false,
wide = false,
inline = true,
shift = false,
} ) => {
const POPOVER_HELPER_WIDTH = 124;
const [ isVisible, setIsVisible ] = useState( false );
const [ hoverTimeout, setHoverTimeout ] = useState( null );
const hideTooltip = useCallback( () => setIsVisible( false ), [ setIsVisible ] );
const toggleTooltip = useCallback(
e => {
Expand Down Expand Up @@ -80,33 +78,8 @@ const IconTooltip: React.FC< IconTooltipProps > = ( {

const isForcedToShow = isAnchorWrapper && forceShow;

const handleMouseEnter = useCallback( () => {
if ( hoverShow ) {
if ( hoverTimeout ) {
clearTimeout( hoverTimeout );
setHoverTimeout( null );
}
setIsVisible( true );
}
}, [ hoverShow, hoverTimeout ] );

const handleMouseLeave = useCallback( () => {
if ( hoverShow ) {
const id = setTimeout( () => {
setIsVisible( false );
setHoverTimeout( null );
}, 100 );
setHoverTimeout( id );
}
}, [ hoverShow ] );

return (
<div
className={ wrapperClassNames }
data-testid="icon-tooltip_wrapper"
onMouseEnter={ handleMouseEnter }
onMouseLeave={ handleMouseLeave }
>
<div className={ wrapperClassNames } data-testid="icon-tooltip_wrapper">
{ ! isAnchorWrapper && (
<Button variant="link" onMouseDown={ toggleTooltip }>
<Gridicon className={ iconClassName } icon={ iconCode } size={ iconSize } />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ export default {
wide: {
control: { type: 'boolean' },
},
hoverShow: {
control: { type: 'boolean' },
},
},
};

Expand Down Expand Up @@ -109,11 +106,3 @@ Wide.args = {
wide: true,
placement: 'bottom-start',
};

export const HoverShow = Template.bind( {} );
HoverShow.args = {
title: 'This is title!',
children: <div>This is a hover tooltip!</div>,
placement: 'bottom-start',
hoverShow: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ export type IconTooltipProps = {
*/
forceShow?: boolean;

/**
* Enables the Popover to show on hover.
*/
hoverShow?: boolean;

/**
* Uses a wider content area when enabled.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Button, Text, ActionPopover } from '@automattic/jetpack-components';
import { CONTACT_SUPPORT_URL, type Threat, fixerStatusIsStale } from '@automattic/jetpack-scan';
import { ExternalLink } from '@wordpress/components';
import { createInterpolateElement, useCallback, useMemo, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import styles from './styles.module.scss';

/**
* Threat Fixer Button component.
*
* @param {object} props - Component props.
* @param {object} props.threat - The threat.
* @param {Function} props.onClick - The onClick function.
* @param {string} props.className - The className.
*
* @return {JSX.Element} The component.
*/
export default function ThreatFixerButton( {
threat,
className,
onClick,
}: {
threat: Threat;
onClick: ( items: Threat[] ) => void;
className?: string;
} ): JSX.Element {
const [ isPopoverVisible, setIsPopoverVisible ] = useState( false );

const [ anchor, setAnchor ] = useState( null );

const children = useMemo( () => {
if ( ! threat.fixable ) {
return null;
}
if ( threat.fixer && threat.fixer.error ) {
return __( 'Error', 'jetpack' );
}
if ( threat.fixer && threat.fixer.status === 'in_progress' ) {
return __( 'Fixing…', 'jetpack' );
}
if ( threat.fixable.fixer === 'delete' ) {
return __( 'Delete', 'jetpack' );
}
if ( threat.fixable.fixer === 'update' ) {
return __( 'Update', 'jetpack' );
}
return __( 'Fix', 'jetpack' );
}, [ threat.fixable, threat.fixer ] );

const errorMessage = useMemo( () => {
if ( threat.fixer && fixerStatusIsStale( threat.fixer ) ) {
return __( 'Fixer is taking longer than expected.', 'jetpack' );
}

if ( threat.fixer && 'error' in threat.fixer && threat.fixer.error ) {
return __( 'An error occurred auto-fixing this threat.', 'jetpack' );
}

return null;
}, [ threat.fixer ] );

const handleClick = useCallback(
( event: React.MouseEvent ) => {
event.stopPropagation();
if ( errorMessage && ! isPopoverVisible ) {
setIsPopoverVisible( true );
return;
}
onClick( [ threat ] );
},
[ onClick, errorMessage, isPopoverVisible, threat ]
);

const closePopover = useCallback( () => {
setIsPopoverVisible( false );
}, [] );

if ( ! threat.fixable ) {
return null;
}

return (
<div>
<Button
size="small"
weight="regular"
variant="secondary"
onClick={ handleClick }
children={ children }
className={ className }
disabled={ threat.fixer && threat.fixer.status === 'in_progress' && ! errorMessage }
isLoading={ threat.fixer && threat.fixer.status === 'in_progress' }
isDestructive={
( threat.fixable && threat.fixable.fixer === 'delete' ) ||
( threat.fixer && threat.fixer.error ) ||
( threat.fixer && fixerStatusIsStale( threat.fixer ) )
}
style={ { minWidth: '72px' } }
ref={ setAnchor }
/>
{ isPopoverVisible && (
<ActionPopover
anchor={ anchor }
buttonContent={ __( 'Retry Fix', 'jetpack' ) }
hideCloseButton={ true }
noArrow={ false }
onClick={ handleClick }
onClose={ closePopover }
title={ __( 'Auto-fix error', 'jetpack' ) }
>
<Text>
{ createInterpolateElement(
sprintf(
/* translators: placeholder is an error message. */
__(
'%s Please try again or <supportLink>contact support</supportLink>.',
'jetpack'
),
errorMessage
),
{
supportLink: (
<ExternalLink
href={ CONTACT_SUPPORT_URL }
className={ styles[ 'support-link' ] }
/>
),
}
) }
</Text>
</ActionPopover>
) }
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ThreatFixerButton from '../index.js';

export default {
title: 'JS Packages/Components/Threat Fixer Button',
component: ThreatFixerButton,
};

export const Default = args => <ThreatFixerButton { ...args } />;
Default.args = {
threat: { fixable: { fixer: 'edit' } },
onClick: () => alert( 'Edit fixer callback triggered' ), // eslint-disable-line no-alert
};

export const Update = args => <ThreatFixerButton { ...args } />;
Update.args = {
threat: { fixable: { fixer: 'update' } },
onClick: () => alert( 'Update fixer callback triggered' ), // eslint-disable-line no-alert
};

export const Delete = args => <ThreatFixerButton { ...args } />;
Delete.args = {
threat: { fixable: { fixer: 'delete' } },
onClick: () => alert( 'Delete fixer callback triggered' ), // eslint-disable-line no-alert
};

export const Loading = args => <ThreatFixerButton { ...args } />;
Loading.args = {
threat: { fixable: { fixer: 'edit' }, fixer: { status: 'in_progress' } },
onClick: () => alert( 'Fixer callback triggered' ), // eslint-disable-line no-alert
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.support-link {
color: inherit;

&:focus,
&:hover {
color: inherit;
box-shadow: none;
}
}
Loading

0 comments on commit f27d02f

Please sign in to comment.