1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

feat: message banner (#2726)

Related to:
https://linear.app/unleash/issue/2-511/exploration-build-data-for-the-possibility-of-showing-an-alert-to-the
Namely:
https://unleash-internal.slack.com/archives/C046LV85N3C/p1671443897386729

The idea is to have a general message banner that can be controlled
through a feature flag in Unleash to display announcements, warnings,
informations, etc.

Currently using mock feature flags, but the idea is to bind this to a
feature flag we can manage in our Unleash instance, and use its payload
to provide information to end users whenever we want.

Co-authored-by: Gastón Fournier <gaston@getunleash.ai>
This commit is contained in:
Nuno Góis 2022-12-22 15:16:51 +00:00 committed by GitHub
parent e533b44c5b
commit aaa96f71cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 1 deletions

View File

@ -0,0 +1,139 @@
import { WarningAmber } from '@mui/icons-material';
import { styled, Icon, Link } from '@mui/material';
import { usePlausibleTracker } from 'hooks/usePlausibleTracker';
import { useNavigate } from 'react-router-dom';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
const StyledBar = styled('aside', {
shouldForwardProp: prop => prop !== 'variant',
})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({
position: 'relative',
zIndex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(1),
gap: theme.spacing(1),
borderBottom: '1px solid',
borderColor: theme.palette[variant].border,
background: theme.palette[variant].light,
color: theme.palette[variant].dark,
}));
const StyledIcon = styled('div', {
shouldForwardProp: prop => prop !== 'variant',
})<{ variant?: BannerVariant }>(({ theme, variant = 'neutral' }) => ({
display: 'flex',
alignItems: 'center',
color: theme.palette[variant].main,
}));
const StyledMessage = styled('div')(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
const StyledLink = styled(Link)(({ theme }) => ({
fontSize: theme.fontSizes.smallBody,
}));
type BannerVariant = 'warning' | 'info' | 'error' | 'success' | 'neutral';
interface IMessageFlag {
enabled: boolean;
message: string;
variant?: BannerVariant;
icon?: string;
link?: string;
linkText?: string;
plausibleEvent?: string;
}
// TODO: Grab a real feature flag instead
const mockFlag: IMessageFlag = {
enabled: true,
message:
'<strong>Heads up!</strong> It seems like one of your client instances might be misbehaving.',
variant: 'warning',
link: '/admin/network',
linkText: 'View Network',
plausibleEvent: 'network_warning',
};
export const MessageBanner = () => {
const { uiConfig } = useUiConfig();
const { enabled, message, variant, icon, link, linkText, plausibleEvent } =
{ ...mockFlag, enabled: uiConfig.flags.messageBanner };
if (!enabled) return null;
return (
<StyledBar variant={variant}>
<StyledIcon variant={variant}>
<BannerIcon icon={icon} variant={variant} />
</StyledIcon>
<StyledMessage dangerouslySetInnerHTML={{ __html: message }} />
<BannerButton
link={link}
linkText={linkText}
plausibleEvent={plausibleEvent}
/>
</StyledBar>
);
};
interface IBannerIconProps {
icon?: string;
variant?: BannerVariant;
}
const BannerIcon = ({ icon, variant }: IBannerIconProps) => {
if (icon === 'none') return null;
if (icon) return <Icon>{icon}</Icon>;
if (variant) return <WarningAmber />;
// TODO: Add defaults for other variants?
return null;
};
interface IBannerButtonProps {
link?: string;
linkText?: string;
plausibleEvent?: string;
}
const BannerButton = ({
link,
linkText = 'More info',
plausibleEvent,
}: IBannerButtonProps) => {
if (!link) return null;
const navigate = useNavigate();
const tracker = usePlausibleTracker();
const external = link.startsWith('http');
const trackEvent = () => {
if (!plausibleEvent) return;
tracker.trackEvent('message_banner', {
props: { event: plausibleEvent },
});
};
if (external)
return (
<StyledLink href={link} target="_blank" onClick={trackEvent}>
{linkText}
</StyledLink>
);
else
return (
<StyledLink
onClick={() => {
trackEvent();
navigate(link);
}}
>
{linkText}
</StyledLink>
);
};

View File

@ -13,7 +13,8 @@ type CustomEvents =
| 'upgrade_plan_clicked'
| 'change_request'
| 'favorite'
| 'maintenance';
| 'maintenance'
| 'message_banner';
export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext);

View File

@ -13,6 +13,7 @@ import { FeedbackCESProvider } from 'component/feedback/FeedbackCESContext/Feedb
import { AnnouncerProvider } from 'component/common/Announcer/AnnouncerProvider/AnnouncerProvider';
import { InstanceStatus } from 'component/common/InstanceStatus/InstanceStatus';
import { UIProviderContainer } from 'component/providers/UIProvider/UIProviderContainer';
import { MessageBanner } from 'component/common/MessageBanner/MessageBanner';
ReactDOM.render(
<UIProviderContainer>
@ -22,6 +23,7 @@ ReactDOM.render(
<AnnouncerProvider>
<FeedbackCESProvider>
<InstanceStatus>
<MessageBanner />
<ScrollTop />
<App />
</InstanceStatus>

View File

@ -46,6 +46,7 @@ export interface IFlags {
variantsPerEnvironment?: boolean;
networkView?: boolean;
maintenance?: boolean;
messageBanner?: boolean;
}
export interface IVersionInfo {

View File

@ -76,6 +76,7 @@ exports[`should create default config 1`] = `
"embedProxyFrontend": true,
"maintenance": false,
"maintenanceMode": false,
"messageBanner": false,
"networkView": false,
"proxyReturnAllToggles": false,
"responseTimeWithAppName": false,
@ -93,6 +94,7 @@ exports[`should create default config 1`] = `
"embedProxyFrontend": true,
"maintenance": false,
"maintenanceMode": false,
"messageBanner": false,
"networkView": false,
"proxyReturnAllToggles": false,
"responseTimeWithAppName": false,

View File

@ -47,6 +47,10 @@ const flags = {
process.env.UNLEASH_EXPERIMENTAL_MAINTENANCE_MODE,
false,
),
messageBanner: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_MESSAGE_BANNER,
false,
),
};
export const defaultExperimentalOptions: IExperimentalOptions = {