diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
index 869cffea7e..614522f83d 100644
--- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
@@ -15,6 +15,7 @@ import {
import { useInitialPathname } from './useInitialPathname';
import { useLastViewedProject } from 'hooks/useLastViewedProject';
import { useLastViewedFlags } from 'hooks/useLastViewedFlags';
+import { NewInUnleash } from './NewInUnleash/NewInUnleash';
export const MobileNavigationSidebar: FC<{ onClick: () => void }> = ({
onClick,
@@ -23,6 +24,7 @@ export const MobileNavigationSidebar: FC<{ onClick: () => void }> = ({
return (
<>
+
{
return (
+ setMode('full')} />
({
+ borderRadius: theme.shape.borderRadiusMedium,
+ [theme.breakpoints.down('lg')]: {
+ margin: theme.spacing(2),
+ marginBottom: theme.spacing(1),
+ },
+}));
+
+const StyledNewInUnleashHeader = styled('p')(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ lineHeight: 1,
+ gap: theme.spacing(1),
+ '& > span': {
+ color: theme.palette.neutral.main,
+ },
+ padding: theme.spacing(1, 2),
+}));
+
+const StyledNewInUnleashList = styled('ul')(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'column',
+ padding: theme.spacing(1),
+ listStyle: 'none',
+ margin: 0,
+ gap: theme.spacing(1),
+}));
+
+const StyledMiniItemButton = styled(ListItemButton)(({ theme }) => ({
+ borderRadius: theme.spacing(0.5),
+ borderLeft: `${theme.spacing(0.5)} solid transparent`,
+ '&.Mui-selected': {
+ borderLeft: `${theme.spacing(0.5)} solid ${theme.palette.primary.main}`,
+ },
+}));
+
+const StyledMiniItemIcon = styled(ListItemIcon)(({ theme }) => ({
+ minWidth: theme.spacing(4),
+ margin: theme.spacing(0.25, 0),
+}));
+
+const StyledSignalsIcon = styled(Signals)(({ theme }) => ({
+ color: theme.palette.primary.main,
+}));
+
+type NewItem = {
+ label: string;
+ icon: ReactNode;
+ link: string;
+ show: boolean;
+};
+
+interface INewInUnleashProps {
+ mode?: NavigationMode;
+ onItemClick?: () => void;
+ onMiniModeClick?: () => void;
+}
+
+export const NewInUnleash = ({
+ mode = 'full',
+ onItemClick,
+ onMiniModeClick,
+}: INewInUnleashProps) => {
+ const { trackEvent } = usePlausibleTracker();
+ const navigate = useNavigate();
+ const [seenItems, setSeenItems] = useLocalStorageState(
+ 'new-in-unleash-seen:v1',
+ new Set(),
+ );
+ const { isEnterprise } = useUiConfig();
+ const signalsEnabled = useUiFlag('signals');
+
+ const items: NewItem[] = [
+ {
+ label: 'Signals & Actions',
+ icon: ,
+ link: '/integrations/signals',
+ show: isEnterprise() && signalsEnabled,
+ },
+ ];
+
+ const visibleItems = items.filter(
+ (item) => item.show && !seenItems.has(item.label),
+ );
+
+ if (!visibleItems.length) return null;
+
+ if (mode === 'mini' && onMiniModeClick) {
+ return (
+
+
+
+
+
+ new_releases
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ new_releases
+ New in Unleash
+
+
+ {visibleItems.map(({ label, icon, link }) => (
+ {
+ trackEvent('new-in-unleash-click', {
+ props: {
+ label,
+ },
+ });
+ navigate(link);
+ onItemClick?.();
+ }}
+ onDismiss={() => {
+ trackEvent('new-in-unleash-dismiss', {
+ props: {
+ label,
+ },
+ });
+ setSeenItems(new Set([...seenItems, label]));
+ }}
+ >
+ {label}
+
+ ))}
+
+
+ );
+};
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleashItem.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleashItem.tsx
new file mode 100644
index 0000000000..93cc126540
--- /dev/null
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NewInUnleash/NewInUnleashItem.tsx
@@ -0,0 +1,66 @@
+import type { ReactNode } from 'react';
+import {
+ IconButton,
+ ListItem,
+ ListItemButton,
+ Tooltip,
+ styled,
+} from '@mui/material';
+import Close from '@mui/icons-material/Close';
+
+const StyledItemButton = styled(ListItemButton)(({ theme }) => ({
+ justifyContent: 'space-between',
+ outline: `1px solid ${theme.palette.divider}`,
+ borderRadius: theme.shape.borderRadiusMedium,
+ padding: theme.spacing(1),
+}));
+
+const StyledItemButtonContent = styled('div')(({ theme }) => ({
+ display: 'flex',
+ alignItems: 'center',
+ gap: theme.spacing(1),
+ fontSize: theme.fontSizes.smallBody,
+}));
+
+const StyledItemButtonClose = styled(IconButton)(({ theme }) => ({
+ padding: theme.spacing(0.25),
+}));
+
+interface INewInUnleashItemProps {
+ icon: ReactNode;
+ onClick: () => void;
+ onDismiss: () => void;
+ children: ReactNode;
+}
+
+export const NewInUnleashItem = ({
+ icon,
+ onClick,
+ onDismiss,
+ children,
+}: INewInUnleashItemProps) => {
+ const onDismissClick = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onDismiss();
+ };
+
+ return (
+
+
+
+ {icon}
+ {children}
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/component/signals/SignalEndpointsTable/SignalEndpointsTable.tsx b/frontend/src/component/signals/SignalEndpointsTable/SignalEndpointsTable.tsx
index 370a72ad4c..d72f2d4c4d 100644
--- a/frontend/src/component/signals/SignalEndpointsTable/SignalEndpointsTable.tsx
+++ b/frontend/src/component/signals/SignalEndpointsTable/SignalEndpointsTable.tsx
@@ -3,7 +3,8 @@ import { TablePlaceholder, VirtualizedTable } from 'component/common/Table';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import useToast from 'hooks/useToast';
import { formatUnknownError } from 'utils/formatUnknownError';
-import { Button, useMediaQuery } from '@mui/material';
+import { Alert, Button, styled, useMediaQuery } from '@mui/material';
+import ReviewsOutlined from '@mui/icons-material/ReviewsOutlined';
import { useFlexLayout, useSortBy, useTable } from 'react-table';
import { sortTypes } from 'utils/sortTypes';
import { TextCell } from 'component/common/Table/cells/TextCell/TextCell';
@@ -25,10 +26,18 @@ import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell';
import { SignalEndpointsSignalsModal } from '../SignalEndpointsSignals/SignalEndpointsSignalsModal';
import { PageContent } from 'component/common/PageContent/PageContent';
import { PageHeader } from 'component/common/PageHeader/PageHeader';
+import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
+import { ADMIN } from '@server/types/permissions';
+import PermissionButton from 'component/common/PermissionButton/PermissionButton';
+import { useFeedback } from 'component/feedbackNew/useFeedback';
export const SignalEndpointsTable = () => {
const { setToastData, setToastApiError } = useToast();
const { uiConfig } = useUiConfig();
+ const { openFeedback, hasSubmittedFeedback } = useFeedback(
+ 'signals',
+ 'automatic',
+ );
const { signalEndpoints, refetch } = useSignalEndpoints();
const { toggleSignalEndpoint, removeSignalEndpoint } =
@@ -44,6 +53,14 @@ export const SignalEndpointsTable = () => {
const [signalsModalOpen, setSignalsModalOpen] = useState(false);
+ const StyledAlert = styled(Alert)(({ theme }) => ({
+ marginBottom: theme.spacing(3),
+ }));
+
+ const StyledParagraph = styled('p')(({ theme }) => ({
+ marginBottom: theme.spacing(2),
+ }));
+
const onToggleSignalEndpoint = async (
{ id, name }: ISignalEndpoint,
enabled: boolean,
@@ -227,69 +244,143 @@ export const SignalEndpointsTable = () => {
{
- setSelectedSignalEndpoint(undefined);
- setModalOpen(true);
- }}
- >
- New signal endpoint
-
+ <>
+ }
+ variant='outlined'
+ onClick={() => {
+ openFeedback({
+ title: 'Do you find signals and actions easy to use?',
+ positiveLabel:
+ 'What do you like most about signals and actions?',
+ areasForImprovementsLabel:
+ 'What needs to change to use signals and actions the way you want?',
+ });
+ }}
+ >
+ Provide feedback
+
+ }
+ />
+ {
+ setSelectedSignalEndpoint(undefined);
+ setModalOpen(true);
+ }}
+ >
+ New signal endpoint
+
+ >
}
/>
}
>
-
-
- No signal endpoints available. Get started by adding
- one.
-
- }
- />
- {
- setNewToken(token);
- setSelectedSignalEndpoint(signalEndpoint);
- setTokenDialog(true);
- }}
- onOpenSignals={() => {
- setModalOpen(false);
- setSignalsModalOpen(true);
- }}
- />
- {
- setSignalsModalOpen(false);
- setModalOpen(true);
- }}
- />
-
-
+
+
+ Signals and actions allow you to respond to events in your
+ real-time monitoring system by automating tasks such as
+ disabling a beta feature in response to an increase in
+ errors or a drop in conversion rates.
+
+
+
+
+ -
+ Signal endpoints are used to send signals to
+ Unleash. This allows you to integrate Unleash with
+ any external tool.
+
+
+ -
+ Actions, which are configured inside
+ projects, allow you to react to those signals and
+ enable or disable flags based on certain conditions.
+
+
+
+
+
+ Read more about these features in our documentation:{' '}
+
+ Signals
+ {' '}
+ and{' '}
+
+ Actions
+
+
+
+
+
+ <>
+
+
+ No signal endpoints available. Get started by
+ adding one.
+
+ }
+ />
+ {
+ setNewToken(token);
+ setSelectedSignalEndpoint(signalEndpoint);
+ setTokenDialog(true);
+ }}
+ onOpenSignals={() => {
+ setModalOpen(false);
+ setSignalsModalOpen(true);
+ }}
+ />
+ {
+ setSignalsModalOpen(false);
+ setModalOpen(true);
+ }}
+ />
+
+
+ >
+
);
};
diff --git a/frontend/src/component/signals/Signals.tsx b/frontend/src/component/signals/Signals.tsx
index 5738938311..b4dff9e6a4 100644
--- a/frontend/src/component/signals/Signals.tsx
+++ b/frontend/src/component/signals/Signals.tsx
@@ -1,5 +1,3 @@
-import { ADMIN } from 'component/providers/AccessProvider/permissions';
-import { PermissionGuard } from 'component/common/PermissionGuard/PermissionGuard';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import { SignalEndpointsTable } from './SignalEndpointsTable/SignalEndpointsTable';
@@ -11,11 +9,5 @@ export const Signals = () => {
return ;
}
- return (
-
- );
+ return ;
};
diff --git a/frontend/src/hooks/usePlausibleTracker.ts b/frontend/src/hooks/usePlausibleTracker.ts
index af7cc1a315..3e70d7b36a 100644
--- a/frontend/src/hooks/usePlausibleTracker.ts
+++ b/frontend/src/hooks/usePlausibleTracker.ts
@@ -61,7 +61,10 @@ export type CustomEvents =
| 'insights-share'
| 'many-strategies'
| 'sdk-banner'
- | 'feature-lifecycle';
+ | 'feature-lifecycle'
+ | 'command-bar'
+ | 'new-in-unleash-click'
+ | 'new-in-unleash-dismiss';
export const usePlausibleTracker = () => {
const plausible = useContext(PlausibleContext);
diff --git a/frontend/src/hooks/useSubmittedFeedback.ts b/frontend/src/hooks/useSubmittedFeedback.ts
index 6e4626c02e..612e238d4a 100644
--- a/frontend/src/hooks/useSubmittedFeedback.ts
+++ b/frontend/src/hooks/useSubmittedFeedback.ts
@@ -4,7 +4,8 @@ export type IFeedbackCategory =
| 'search'
| 'insights'
| 'applicationOverview'
- | 'newProjectOverview';
+ | 'newProjectOverview'
+ | 'signals';
export const useUserSubmittedFeedback = (category: IFeedbackCategory) => {
const key = `unleash-userSubmittedFeedback:${category}`;