feat(paywall blocker): improvements for trial end blocker screen (#5756)

* feat: add view templates option to dashboard menu

* feat: increase dropdown overlay width
Set the dropdown overlay width to 200px to provide breathing space for the dropdown button.
Added flex to wrap the dropdown button to create space between the right icon and the left elements.

* feat(paywall blocker): improvements for trial end blocker screen

- added new components locally for rendering static contents
- fixed SCSS code for better readablity
- seperated data to specific file
- added alert info style for the non admin users message

* chore: fixed few conditions

* feat(paywall title): added contact us to modal title

* feat: non admin users communication styles

* chore: added useState for the sidebar collapse state to be false

* test(WorkspaceLocked): update Jest test to sync with recent UX copy changes

* feat(workspaceLocked): added locale

added English and English-GB translations for workspace locked messages

* feat: reverted the translation for and sidebar collapse fix

- I have removed the scope for unitest having locale support
- remove the useEffect way to set sidebar collapse, instead added it in app layout
- removed the opacity effect on tabs

* refactor(workspaceLocked): refactor appLayout component to simplify the isWorkspaceLocked function

* refactor(workspaceLocked): simplify isWorkspaceLocked by converting it to a constant expression

* refactor(workspaceLocked): refactor modal classname and variable

---------

Co-authored-by: Pranay Prateek <pranay@signoz.io>
This commit is contained in:
Sudeep MP 2024-09-06 14:26:13 +01:00 committed by GitHub
parent 0db2784d6b
commit ae857d3fcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 714 additions and 71 deletions

View File

@ -0,0 +1,22 @@
{
"trialPlanExpired": "Trial Plan Expired",
"gotQuestions": "Got Questions?",
"contactUs": "Contact Us",
"upgradeToContinue": "Upgrade to Continue",
"upgradeNow": "Upgrade now to keep enjoying all the great features youve been using.",
"yourDataIsSafe": "Your data is safe with us until",
"actNow": "Act now to avoid any disruptions and continue where you left off.",
"contactAdmin": "Contact your admin to proceed with the upgrade.",
"continueMyJourney": "Continue My Journey",
"needMoreTime": "Need More Time?",
"extendTrial": "Extend Trial",
"extendTrialMsgPart1": "If you have a specific reason why you were not able to finish your PoC in the trial period, please write to us on",
"extendTrialMsgPart2": "with the reason. Sometimes we can extend trial by a few days on a case by case basis",
"whyChooseSignoz": "Why choose Signoz",
"enterpriseGradeObservability": "Enterprise-grade Observability",
"observabilityDescription": "Get access to observability at any scale with advanced security and compliance.",
"continueToUpgrade": "Continue to Upgrade",
"youAreInGoodCompany": "You are in good company",
"faqs": "FAQs",
"somethingWentWrong": "Something went wrong"
}

View File

@ -0,0 +1,22 @@
{
"trialPlanExpired": "Trial Plan Expired",
"gotQuestions": "Got Questions?",
"contactUs": "Contact Us",
"upgradeToContinue": "Upgrade to Continue",
"upgradeNow": "Upgrade now to keep enjoying all the great features youve been using.",
"yourDataIsSafe": "Your data is safe with us until",
"actNow": "Act now to avoid any disruptions and continue where you left off.",
"contactAdmin": "Contact your admin to proceed with the upgrade.",
"continueMyJourney": "Continue My Journey",
"needMoreTime": "Need More Time?",
"extendTrial": "Extend Trial",
"extendTrialMsgPart1": "If you have a specific reason why you were not able to finish your PoC in the trial period, please write to us on",
"extendTrialMsgPart2": "with the reason. Sometimes we can extend trial by a few days on a case by case basis",
"whyChooseSignoz": "Why choose Signoz",
"enterpriseGradeObservability": "Enterprise-grade Observability",
"observabilityDescription": "Get access to observability at any scale with advanced security and compliance.",
"continueToUpgrade": "Continue to Upgrade",
"youAreInGoodCompany": "You are in good company",
"faqs": "FAQs",
"somethingWentWrong": "Something went wrong"
}

View File

@ -214,7 +214,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const pageTitle = t(routeKey);
const renderFullScreen =
pathname === ROUTES.GET_STARTED ||
pathname === ROUTES.WORKSPACE_LOCKED ||
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
@ -282,6 +281,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const isSideNavCollapsed = getLocalStorageKey(IS_SIDEBAR_COLLAPSED);
/**
* Note: Right now we don't have a page-level method to pass the sidebar collapse state.
* Since the use case for overriding is not widely needed, we are setting it here
* so that the workspace locked page will have an expanded sidebar regardless of how users
* have set it or what is stored in localStorage. This will not affect the localStorage config.
*/
const isWorkspaceLocked = pathname === ROUTES.WORKSPACE_LOCKED;
return (
<Layout
className={cx(
@ -326,7 +333,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
licenseData={licenseData}
isFetching={isFetching}
onCollapse={onCollapse}
collapsed={collapsed}
collapsed={isWorkspaceLocked ? false : collapsed}
/>
)}
<div

View File

@ -0,0 +1,35 @@
import './customerStoryCard.styles.scss';
import { Avatar, Card, Space } from 'antd';
interface CustomerStoryCardProps {
avatar: string;
personName: string;
role: string;
message: string;
link: string;
}
function CustomerStoryCard({
avatar,
personName,
role,
message,
link,
}: CustomerStoryCardProps): JSX.Element {
return (
<a href={link} target="_blank" rel="noopener noreferrer">
<Card className="customer-story-card">
<Space size="middle" direction="vertical">
<Card.Meta
avatar={<Avatar size={48} src={avatar} />}
title={personName}
description={role}
/>
{message}
</Space>
</Card>
</a>
);
}
export default CustomerStoryCard;

View File

@ -0,0 +1,30 @@
import { Col, Row, Space, Typography } from 'antd';
interface InfoItem {
title: string;
description: string;
id: string; // Add a unique identifier
}
interface InfoBlocksProps {
items: InfoItem[];
}
function InfoBlocks({ items }: InfoBlocksProps): JSX.Element {
return (
<Space direction="vertical" size="middle">
{items.map((item) => (
<Row gutter={8} key={item.id}>
<Col span={24}>
<Typography.Title level={5}>{item.title}</Typography.Title>
</Col>
<Col span={24}>
<Typography>{item.description}</Typography>
</Col>
</Row>
))}
</Space>
);
}
export default InfoBlocks;

View File

@ -1,16 +1,161 @@
.workspace-locked-container {
text-align: center;
padding: 48px;
margin: 24px;
$light-theme: 'lightMode';
@keyframes gradientFlow {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
.workpace-locked-details {
width: 50%;
.workspace-locked {
&__modal {
.ant-modal-mask {
backdrop-filter: blur(2px);
}
}
&__tabs {
margin-top: 148px;
.ant-tabs {
&-nav {
&::before {
border-color: var(--bg-slate-500);
.#{$light-theme} & {
border-color: var(--bg-vanilla-300);
}
}
}
&-nav-wrap {
justify-content: center;
}
}
}
&__modal {
&__header {
display: flex;
justify-content: space-between;
align-items: center;
&__actions {
display: flex;
align-items: center;
gap: 16px;
}
}
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--bg-slate-400);
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
.#{$light-theme} & {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
.ant-modal-header {
background: transparent;
}
.ant-list {
&-item {
border-color: var(--bg-slate-500);
.#{$light-theme} & {
border-color: var(--bg-vanilla-300);
}
&-meta {
align-items: center !important;
&-title {
margin-bottom: 0 !important;
}
&-avatar {
display: flex;
}
}
}
}
&__title {
font-weight: 400;
color: var(--text-vanilla-400);
.#{$light-theme} & {
color: var(--text-ink-200);
}
}
&__cta {
margin-top: 54px;
}
}
&__container {
padding-top: 64px;
}
&__details {
width: 80%;
margin: 0 auto;
color: var(--text-vanilla-400, #c0c1c3);
text-align: center;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
.#{$light-theme} & {
color: var(--text-ink-200);
}
&__highlight {
color: var(--text-vanilla-100, #fff);
font-style: normal;
font-weight: 700;
line-height: 24px;
.#{$light-theme} & {
color: var(--text-ink-100);
}
}
}
&__title {
background: linear-gradient(
99deg,
#ead8fd 0%,
#7a97fa 33%,
#fd5ab2 66%,
#ead8fd 100%
);
background-size: 300% 300%;
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientFlow 24s ease infinite;
margin-bottom: 18px;
}
}
.contact-us {
margin-top: 48px;
color: var(--text-vanilla-400);
.#{$light-theme} & {
color: var(--text-ink-200);
}
}
.cta {

View File

@ -20,17 +20,17 @@ describe('WorkspaceLocked', () => {
});
const workspaceLocked = await screen.findByRole('heading', {
name: /workspace locked/i,
name: /upgrade to continue/i,
});
expect(workspaceLocked).toBeInTheDocument();
const gotQuestionText = await screen.findByText(/got question?/i);
expect(gotQuestionText).toBeInTheDocument();
const contactUsLink = await screen.findByRole('link', {
name: /contact us/i,
const contactUsBtn = await screen.findByRole('button', {
name: /Contact Us/i,
});
expect(contactUsLink).toBeInTheDocument();
expect(contactUsBtn).toBeInTheDocument();
});
test('Render for Admin', async () => {
@ -42,11 +42,11 @@ describe('WorkspaceLocked', () => {
render(<WorkspaceLocked />);
const contactAdminMessage = await screen.queryByText(
/please contact your administrator for further help/i,
/contact your admin to proceed with the upgrade./i,
);
expect(contactAdminMessage).not.toBeInTheDocument();
const updateCreditCardBtn = await screen.findByRole('button', {
name: /update credit card/i,
name: /continue my journey/i,
});
expect(updateCreditCardBtn).toBeInTheDocument();
});
@ -60,12 +60,12 @@ describe('WorkspaceLocked', () => {
render(<WorkspaceLocked />, {}, 'VIEWER');
const updateCreditCardBtn = await screen.queryByRole('button', {
name: /update credit card/i,
name: /Continue My Journey/i,
});
expect(updateCreditCardBtn).not.toBeInTheDocument();
const contactAdminMessage = await screen.findByText(
/please contact your administrator for further help/i,
/contact your admin to proceed with the upgrade./i,
);
expect(contactAdminMessage).toBeInTheDocument();
});

View File

@ -1,21 +1,30 @@
/* eslint-disable react/no-unescaped-entities */
import './WorkspaceLocked.styles.scss';
import type { TabsProps } from 'antd';
import {
CreditCardOutlined,
LockOutlined,
SendOutlined,
} from '@ant-design/icons';
import { Button, Card, Skeleton, Typography } from 'antd';
Alert,
Button,
Col,
Collapse,
Flex,
List,
Modal,
Row,
Skeleton,
Space,
Tabs,
Typography,
} from 'antd';
import updateCreditCardApi from 'api/billing/checkout';
import logEvent from 'api/common/logEvent';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import ROUTES from 'constants/routes';
import FullScreenHeader from 'container/FullScreenHeader/FullScreenHeader';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { CircleArrowRight } from 'lucide-react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@ -23,13 +32,22 @@ import { License } from 'types/api/licenses/def';
import AppReducer from 'types/reducer/app';
import { getFormattedDate } from 'utils/timeUtils';
import CustomerStoryCard from './CustomerStoryCard';
import InfoBlocks from './InfoBlocks';
import {
customerStoriesData,
enterpriseGradeValuesData,
faqData,
infoData,
} from './workspaceLocked.data';
export default function WorkspaceBlocked(): JSX.Element {
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const isAdmin = role === 'ADMIN';
const [activeLicense, setActiveLicense] = useState<License | null>(null);
const { notifications } = useNotifications();
const { t } = useTranslation(['workspaceLocked']);
const {
isFetching: isFetchingLicenseData,
isLoading: isLoadingLicenseData,
@ -67,7 +85,7 @@ export default function WorkspaceBlocked(): JSX.Element {
},
onError: () =>
notifications.error({
message: SOMETHING_WENT_WRONG,
message: t('somethingWentWrong'),
}),
},
);
@ -87,73 +105,248 @@ export default function WorkspaceBlocked(): JSX.Element {
logEvent('Workspace Blocked: User Clicked Extend Trial', {});
notifications.info({
message: 'Extend Trial',
message: t('extendTrial'),
duration: 0,
description: (
<Typography>
If you have a specific reason why you were not able to finish your PoC in
the trial period, please write to us on
<a href="mailto:cloud-support@signoz.io"> cloud-support@signoz.io </a>
with the reason. Sometimes we can extend trial by a few days on a case by
case basis
{t('extendTrialMsgPart1')}{' '}
<a href="mailto:cloud-support@signoz.io">cloud-support@signoz.io</a>{' '}
{t('extendTrialMsgPart2')}
</Typography>
),
});
};
return (
<>
<FullScreenHeader overrideRoute={ROUTES.WORKSPACE_LOCKED} />
const renderCustomerStories = (
filterCondition: (index: number) => boolean,
): JSX.Element[] =>
customerStoriesData
.filter((_, index) => filterCondition(index))
.map((story) => (
<CustomerStoryCard
avatar={story.avatar}
personName={story.personName}
role={story.role}
message={story.message}
link={story.link}
key={story.key}
/>
));
<Card className="workspace-locked-container">
{isLoadingLicenseData || !licensesData?.payload?.workSpaceBlock ? (
<Skeleton />
) : (
<>
<LockOutlined style={{ fontSize: '36px', color: '#08c' }} />
<Typography.Title level={4}> Workspace Locked </Typography.Title>
<Typography.Paragraph className="workpace-locked-details">
You have been locked out of your workspace because your trial ended
without an upgrade to a paid plan. Your data will continue to be ingested
till{' '}
{getFormattedDate(licensesData?.payload?.gracePeriodEnd || Date.now())} ,
at which point we will drop all the ingested data and terminate the
account.
{!isAdmin && 'Please contact your administrator for further help'}
</Typography.Paragraph>
<div className="cta">
const tabItems: TabsProps['items'] = [
{
key: '1',
label: t('whyChooseSignoz'),
children: (
<Row align="middle" justify="center">
<Col span={12}>
<Row gutter={[24, 48]}>
<Col span={24}>
<InfoBlocks items={infoData} />
</Col>
<Col span={24}>
<Space size="large" direction="vertical">
<Flex vertical>
<Typography.Title level={3}>
{t('enterpriseGradeObservability')}
</Typography.Title>
<Typography>{t('observabilityDescription')}</Typography>
</Flex>
<List
itemLayout="horizontal"
dataSource={enterpriseGradeValuesData}
renderItem={(item, index): React.ReactNode => (
<List.Item key={index}>
<List.Item.Meta avatar={<CircleArrowRight />} title={item.title} />
</List.Item>
)}
/>
</Space>
</Col>
{isAdmin && (
<Col span={24}>
<Button
className="update-credit-card-btn"
type="primary"
icon={<CreditCardOutlined />}
shape="round"
size="middle"
loading={isLoading}
onClick={handleUpdateCreditCard}
>
Update Credit Card
{t('continueToUpgrade')}
</Button>
</Col>
)}
</Row>
</Col>
</Row>
),
},
{
key: '2',
label: t('youAreInGoodCompany'),
children: (
<Row gutter={[24, 16]} justify="center">
{/* #FIXME: please suggest if there is any better way to loop in different columns to get the masonry layout */}
<Col span={10}>{renderCustomerStories((index) => index % 2 === 0)}</Col>
<Col span={10}>{renderCustomerStories((index) => index % 2 !== 0)}</Col>
{isAdmin && (
<Col span={24}>
<Flex justify="center">
<Button
type="primary"
shape="round"
size="middle"
loading={isLoading}
onClick={handleUpdateCreditCard}
>
{t('continueToUpgrade')}
</Button>
</Flex>
</Col>
)}
</Row>
),
},
// #TODO: comming soon
// {
// key: '3',
// label: 'Our Pricing',
// children: 'Our Pricing',
// },
{
key: '4',
label: t('faqs'),
children: (
<Row align="middle" justify="center">
<Col span={18}>
<Space size="large" direction="vertical">
<Collapse items={faqData} defaultActiveKey={['1']} />
{isAdmin && (
<Button
type="primary"
shape="round"
size="middle"
loading={isLoading}
onClick={handleUpdateCreditCard}
>
{t('continueToUpgrade')}
</Button>
)}
</Space>
</Col>
</Row>
),
},
];
return (
<div>
<Modal
rootClassName="workspace-locked__modal"
title={
<div className="workspace-locked__modal__header">
<span className="workspace-locked__modal__title">
{t('trialPlanExpired')}
</span>
<span className="workspace-locked__modal__header__actions">
<Typography.Text className="workspace-locked__modal__title">
Got Questions?
</Typography.Text>
<Button
className="extend-trial-btn"
type="default"
icon={<SendOutlined />}
shape="round"
size="middle"
href="mailto:cloud-support@signoz.io"
role="button"
>
Contact Us
</Button>
</span>
</div>
}
open
closable={false}
footer={null}
width="65%"
>
<div className="workspace-locked__container">
{isLoadingLicenseData || !licensesData ? (
<Skeleton />
) : (
<>
<Row justify="center" align="middle">
<Col>
<Space direction="vertical" align="center">
<Typography.Title level={2}>
<div className="workspace-locked__title">Upgrade to Continue</div>
</Typography.Title>
<Typography.Paragraph className="workspace-locked__details">
{t('upgradeNow')}
<br />
{t('yourDataIsSafe')}{' '}
<span className="workspace-locked__details__highlight">
{getFormattedDate(
licensesData.payload?.gracePeriodEnd || Date.now(),
)}
</span>{' '}
{t('actNow')}
</Typography.Paragraph>
</Space>
</Col>
</Row>
{!isAdmin && (
<Row
justify="center"
align="middle"
className="workspace-locked__modal__cta"
gutter={[16, 16]}
>
<Col>
<Alert
message="Contact your admin to proceed with the upgrade."
type="info"
/>
</Col>
</Row>
)}
{isAdmin && (
<Row
justify="center"
align="middle"
className="workspace-locked__modal__cta"
gutter={[16, 16]}
>
<Col>
<Button
type="primary"
shape="round"
size="middle"
loading={isLoading}
onClick={handleUpdateCreditCard}
>
continue my journey
</Button>
</Col>
<Col>
<Button
type="default"
shape="round"
size="middle"
onClick={handleExtendTrial}
>
Extend Trial
{t('needMoreTime')}
</Button>
</div>
<div className="contact-us">
Got Questions?
<span>
<a href="mailto:cloud-support@signoz.io"> Contact Us </a>
</span>
</div>
</Col>
</Row>
)}
<Flex justify="center" className="workspace-locked__tabs">
<Tabs items={tabItems} defaultActiveKey="2" />
</Flex>
</>
)}
</Card>
</>
</div>
</Modal>
</div>
);
}

View File

@ -0,0 +1,33 @@
$component-name: 'customer-story-card';
$ant-card-override: 'ant-card';
$light-theme: 'lightMode';
.#{$component-name} {
max-width: 385px;
margin: 0 auto; // Center the card within the column
margin-bottom: 24px;
border-radius: 6px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
background-color: var(--bg-ink-400);
border: 1px solid var(--bg-ink-300);
.#{$light-theme} & {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
.#{$ant-card-override}-meta-title {
margin-bottom: 2px !important;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
background-color: var(--bg-ink-300);
.#{$light-theme} & {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: var(--bg-vanilla-100);
}
}
}

View File

@ -0,0 +1,156 @@
export const infoData = [
{
id: 'infoBlock-1',
title: 'Built for scale',
description:
'Our powerful ingestion engine has a proven track record of handling 10TB+ data ingestion per day.',
},
{
id: 'infoBlock-2',
title: 'Trusted across the globe',
description:
'Used by teams in all 5 continents ⎯ across the mountains, rivers, and the high seas.',
},
{
id: 'infoBlock-3',
title: 'Powering observability for teams of all sizes',
description:
'Hundreds of companies ⎯from early-stage start-ups to public enterprises use SigNoz to build more reliable products.',
},
];
export const enterpriseGradeValuesData = [
{
title: 'SSO and SAML support',
},
{
title: 'Query API keys',
},
{
title: 'Advanced security with SOC 2 Type I certification',
},
{
title: 'AWS Private Link',
},
{
title: 'VPC peering',
},
{
title: 'Custom integrations',
},
];
export const customerStoriesData = [
{
key: 'c-story-1',
avatar: 'https://signoz.io/img/users/subomi-oluwalana.webp',
personName: 'Subomi Oluwalana',
role: 'Founder & CEO at Convoy',
customerName: 'Convoy',
message:
"We use OTel with SigNoz to spot redundant database connect calls. For example, we found that our database driver wasn't using the connection pool even though the documentation claimed otherwise.",
link:
'https://www.linkedin.com/feed/update/urn:li:activity:7212117589068591105/',
},
{
key: 'c-story-2',
avatar: 'https://signoz.io/img/users/dhruv-garg.webp',
personName: 'Dhruv Garg',
role: 'Tech Lead at Nudge',
customerName: 'Nudge',
message:
'SigNoz is one of the best observability tools you can self-host hands down. And they are always there to help on their slack channel when needed.',
link:
'https://www.linkedin.com/posts/dhruv-garg79_signoz-docker-kubernetes-activity-7205163679028240384-Otlb/',
},
{
key: 'c-story-3',
avatar: 'https://signoz.io/img/users/vivek-bhakta.webp',
personName: 'Vivek Bhakta',
role: 'CTO at Wombo AI',
customerName: 'Wombo AI',
message:
'We use SigNoz and have been loving it - can definitely handle scale.',
link: 'https://x.com/notorious_VB/status/1701773119696904242',
},
{
key: 'c-story-4',
avatar: 'https://signoz.io/img/users/pranay-narang.webp',
personName: 'Pranay Narang',
role: 'Engineering at Azodha',
customerName: 'Azodha',
message:
'Recently moved metrics and logging to SigNoz. Gotta say, absolutely loving the tool.',
link: 'https://x.com/PranayNarang/status/1676247073396752387',
},
{
key: 'c-story-4',
avatar: 'https://signoz.io/img/users/shey.webp',
personName: 'Sheheryar Sewani',
role: 'Seasoned Rails Dev & Founder',
customerName: '',
message:
"But wow, I'm glad I tried SigNoz. Setting up SigNoz was easy—they provide super helpful instructions along with a docker-compose file.",
link:
'https://www.linkedin.com/feed/update/urn:li:activity:7181011853915926528/',
},
{
key: 'c-story-5',
avatar: 'https://signoz.io/img/users/daniel.webp',
personName: 'Daniel Schell',
role: 'Founder & CTO at Airlockdigital',
customerName: 'Airlockdigital',
message:
'Have been deep diving Signoz. Seems like the new hotness for an "all-in-one".',
link: 'https://x.com/danonit/status/1749256583157284919',
},
{
key: 'c-story-6',
avatar: 'https://signoz.io/img/users/go-frendi.webp',
personName: 'Go Frendi Gunawan',
role: 'Data Engineer at Ctlyst.id',
customerName: 'Ctlyst.id',
message:
'Monitoring done. Thanks to SigNoz, I dont have to deal with Grafana, Loki, Prometheus, and Jaeger separately.',
link: 'https://x.com/gofrendiasgard/status/1680139003658641408',
},
{
key: 'c-story-7',
avatar: 'https://signoz.io/img/users/anselm.jpg',
personName: 'Anselm Eickhoff',
role: 'Software Architect',
customerName: '',
message:
'NewRelic: receiving OpenTelemetry at all takes me 1/2 day to grok, docs are a mess. Traces show up after 5min. I burn the free 100GB/mo in 1 day of light testing. @SignozHQ: can run it locally (∞GB), has a special tutorial for OpenTelemetry + Rust! Traces show up immediately.',
link:
'https://twitter.com/ae_play/status/1572993932094472195?s=20&t=LWWrW5EP_k5q6_mwbFN4jQ',
},
];
export const faqData = [
{
key: '1',
label:
'What is the difference between SigNoz Cloud(Teams) and Community Edition?',
children:
'You can self-host and manage the community edition yourself. You should choose SigNoz Cloud if you dont want to worry about managing the SigNoz cluster. There are some exclusive features like SSO & SAML support, which come with SigNoz cloud offering. Our team also offers support on the initial configuration of dashboards & alerts and advises on best practices for setting up your observability stack in the SigNoz cloud offering.',
},
{
key: '2',
label: 'How are number of samples calculated for metrics pricing?',
children:
"If a timeseries sends data every 30s, then it will generate 2 samples per min. So, if you have 10,000 time series sending data every 30s then you will be sending 20,000 samples per min to SigNoz. This will be around 864 mn samples per month and would cost 86.4 USD/month. Here's an explainer video on how metrics pricing is calculated - Link: https://vimeo.com/973012522",
},
{
key: '3',
label: 'Do you offer enterprise support plans?',
children:
'Yes, feel free to reach out to us on hello@signoz.io if you need a dedicated support plan or paid support for setting up your initial SigNoz setup.',
},
{
key: '4',
label: 'Who should use Enterprise plans?',
children:
'Teams which need enterprise support or features like SSO, Audit logs, etc. may find our enterprise plans valuable.',
},
];