diff --git a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx
index f31fad1aa2..0936c4754d 100644
--- a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx
+++ b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx
@@ -9,7 +9,10 @@ import cx from 'classnames';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext';
-import { useCases } from 'container/OnboardingContainer/OnboardingContainer';
+import {
+ ModulesMap,
+ useCases,
+} from 'container/OnboardingContainer/OnboardingContainer';
import {
getDataSources,
getSupportedFrameworks,
@@ -49,6 +52,9 @@ export default function DataSource(): JSX.Element {
updateSelectedFramework,
} = useOnboardingContext();
+ const isKafkaAPM =
+ getStartedSource === 'kafka' && selectedModule?.id === ModulesMap.APM;
+
const [supportedDataSources, setSupportedDataSources] = useState<
DataSourceType[]
>([]);
@@ -155,14 +161,14 @@ export default function DataSource(): JSX.Element {
className={cx(
'supported-language',
selectedDataSource?.name === dataSource.name ? 'selected' : '',
- getStartedSource === 'kafka' &&
+ isKafkaAPM &&
!messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '')
? 'disabled'
: '',
)}
key={dataSource.name}
onClick={(): void => {
- if (getStartedSource !== 'kafka') {
+ if (!isKafkaAPM) {
updateSelectedFramework(null);
updateSelectedEnvironment(null);
updateSelectedDataSource(dataSource);
diff --git a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
index df5e296722..b91d13d3bb 100644
--- a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
+++ b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts
@@ -252,7 +252,8 @@ import APM_java_springBoot_docker_recommendedSteps_runApplication from '../Modul
import APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-installOtelCollector.md';
import APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-instrumentApplication.md';
import APM_java_springBoot_kubernetes_recommendedSteps_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication.md';
-import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producer from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md';
+import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_consumers from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-consumers.md';
+import APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers from '../Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producers.md';
// SpringBoot-LinuxAMD64-quickstart
import APM_java_springBoot_linuxAMD64_quickStart_instrumentApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-instrumentApplication.md';
import APM_java_springBoot_linuxAMD64_quickStart_runApplication from '../Modules/APM/Java/md-docs/SpringBoot/LinuxAMD64/QuickStart/springBoot-linuxamd64-quickStart-runApplication.md';
@@ -1054,7 +1055,8 @@ export const ApmDocFilePaths = {
APM_java_springBoot_kubernetes_recommendedSteps_setupOtelCollector,
APM_java_springBoot_kubernetes_recommendedSteps_instrumentApplication,
APM_java_springBoot_kubernetes_recommendedSteps_runApplication,
- APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producer,
+ APM_java_springBoot_kubernetes_recommendedSteps_runApplication_producers,
+ APM_java_springBoot_kubernetes_recommendedSteps_runApplication_consumers,
// SpringBoot-LinuxAMD64-recommended
APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector,
diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx
index ee7606ff3f..1c061803be 100644
--- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx
+++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx
@@ -82,7 +82,7 @@ export function AboutSigNozQuestions({
otherInterestInSignoz,
});
- logEvent('User Onboarding: About SigNoz Questions Answered', {
+ logEvent('Org Onboarding: Answered', {
hearAboutSignoz,
otherAboutSignoz,
interestInSignoz,
diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx
index fef689de3a..def1cf979d 100644
--- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx
+++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx
@@ -161,6 +161,13 @@ function InviteTeamMembers({
setInviteUsersSuccessResponse(successfulInvites);
+ logEvent('Org Onboarding: Invite Team Members Success', {
+ teamMembers: teamMembersToInvite,
+ totalInvites: inviteUsersResponse.summary.total_invites,
+ successfulInvites: inviteUsersResponse.summary.successful_invites,
+ failedInvites: inviteUsersResponse.summary.failed_invites,
+ });
+
setTimeout(() => {
setDisableNextButton(false);
onNext();
@@ -172,6 +179,13 @@ function InviteTeamMembers({
setInviteUsersSuccessResponse(successfulInvites);
+ logEvent('Org Onboarding: Invite Team Members Partial Success', {
+ teamMembers: teamMembersToInvite,
+ totalInvites: inviteUsersResponse.summary.total_invites,
+ successfulInvites: inviteUsersResponse.summary.successful_invites,
+ failedInvites: inviteUsersResponse.summary.failed_invites,
+ });
+
if (inviteUsersResponse.failed_invites.length > 0) {
setHasErrors(true);
@@ -182,27 +196,21 @@ function InviteTeamMembers({
}
};
- const {
- mutate: sendInvites,
- isLoading: isSendingInvites,
- data: inviteUsersApiResponseData,
- } = useMutation(inviteUsers, {
- onSuccess: (response: SuccessResponse
): void => {
- logEvent('User Onboarding: Invite Team Members Sent', {
- teamMembers: teamMembersToInvite,
- });
-
- handleInviteUsersSuccess(response);
+ const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation(
+ inviteUsers,
+ {
+ onSuccess: (response: SuccessResponse): void => {
+ handleInviteUsersSuccess(response);
+ },
+ onError: (error: AxiosError): void => {
+ logEvent('Org Onboarding: Invite Team Members Failed', {
+ teamMembers: teamMembersToInvite,
+ });
+
+ handleError(error);
+ },
},
- onError: (error: AxiosError): void => {
- logEvent('User Onboarding: Invite Team Members Failed', {
- teamMembers: teamMembersToInvite,
- error,
- });
-
- handleError(error);
- },
- });
+ );
const handleNext = (): void => {
if (validateAllUsers()) {
@@ -254,9 +262,8 @@ function InviteTeamMembers({
};
const handleDoLater = (): void => {
- logEvent('User Onboarding: Invite Team Members Skipped', {
- teamMembers: teamMembersToInvite,
- apiResponse: inviteUsersApiResponseData,
+ logEvent('Org Onboarding: Clicked Do Later', {
+ currentPageID: 4,
});
onNext();
diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx
index f1be6fb8ee..dc499c9308 100644
--- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx
+++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx
@@ -122,7 +122,7 @@ function OptimiseSignozNeeds({
}, [services, hostsPerDay, logsPerDay]);
const handleOnNext = (): void => {
- logEvent('User Onboarding: Optimise SigNoz Needs Answered', {
+ logEvent('Org Onboarding: Answered', {
logsPerDay,
hostsPerDay,
services,
@@ -144,10 +144,8 @@ function OptimiseSignozNeeds({
onWillDoLater();
- logEvent('User Onboarding: Optimise SigNoz Needs Skipped', {
- logsPerDay: 0,
- hostsPerDay: 0,
- services: 0,
+ logEvent('Org Onboarding: Clicked Do Later', {
+ currentPageID: 3,
});
};
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx
index e0376a6559..7569e0fa81 100644
--- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx
+++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx
@@ -94,6 +94,13 @@ function OrgQuestions({
organisationName === '' ||
orgDetails.organisationName === organisationName
) {
+ logEvent('Org Onboarding: Answered', {
+ usesObservability,
+ observabilityTool,
+ otherTool,
+ familiarity,
+ });
+
onNext({
organisationName,
usesObservability,
@@ -121,10 +128,17 @@ function OrgQuestions({
},
});
- logEvent('User Onboarding: Org Name Updated', {
+ logEvent('Org Onboarding: Org Name Updated', {
organisationName: orgDetails.organisationName,
});
+ logEvent('Org Onboarding: Answered', {
+ usesObservability,
+ observabilityTool,
+ otherTool,
+ familiarity,
+ });
+
onNext({
organisationName,
usesObservability,
@@ -133,7 +147,7 @@ function OrgQuestions({
familiarity,
});
} else {
- logEvent('User Onboarding: Org Name Update Failed', {
+ logEvent('Org Onboarding: Org Name Update Failed', {
organisationName: orgDetails.organisationName,
});
diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx
index 3b3ed59354..390ac00212 100644
--- a/frontend/src/container/OnboardingQuestionaire/index.tsx
+++ b/frontend/src/container/OnboardingQuestionaire/index.tsx
@@ -1,6 +1,7 @@
import './OnboardingQuestionaire.styles.scss';
import { NotificationInstance } from 'antd/es/notification/interface';
+import logEvent from 'api/common/logEvent';
import updateProfileAPI from 'api/onboarding/updateProfile';
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference';
@@ -61,6 +62,10 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = {
services: 0,
};
+const BACK_BUTTON_EVENT_NAME = 'Org Onboarding: Back Button Clicked';
+const NEXT_BUTTON_EVENT_NAME = 'Org Onboarding: Next Button Clicked';
+const ONBOARDING_COMPLETE_EVENT_NAME = 'Org Onboarding: Complete';
+
function OnboardingQuestionaire(): JSX.Element {
const { notifications } = useNotifications();
const { org } = useSelector((state) => state.app);
@@ -98,6 +103,13 @@ function OnboardingQuestionaire(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [org]);
+ useEffect(() => {
+ logEvent('Org Onboarding: Started', {
+ org_id: org?.[0]?.id,
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
const { refetch: refetchOrgPreferences } = useQuery({
queryFn: () => getAllOrgPreferences(),
queryKey: ['getOrgPreferences'],
@@ -120,6 +132,8 @@ function OnboardingQuestionaire(): JSX.Element {
setUpdatingOrgOnboardingStatus(false);
+ logEvent('Org Onboarding: Redirecting to Get Started', {});
+
history.push(ROUTES.GET_STARTED);
},
onError: () => {
@@ -156,6 +170,11 @@ function OnboardingQuestionaire(): JSX.Element {
});
const handleUpdateProfile = (): void => {
+ logEvent(NEXT_BUTTON_EVENT_NAME, {
+ currentPageID: 3,
+ nextPageID: 4,
+ });
+
updateProfile({
familiarity_with_observability: orgDetails?.familiarity as string,
has_existing_observability_tool: orgDetails?.usesObservability as boolean,
@@ -180,6 +199,10 @@ function OnboardingQuestionaire(): JSX.Element {
};
const handleOnboardingComplete = (): void => {
+ logEvent(ONBOARDING_COMPLETE_EVENT_NAME, {
+ currentPageID: 4,
+ });
+
setUpdatingOrgOnboardingStatus(true);
updateOrgPreference({
preferenceID: 'ORG_ONBOARDING',
@@ -199,6 +222,11 @@ function OnboardingQuestionaire(): JSX.Element {
currentOrgData={currentOrgData}
orgDetails={orgDetails}
onNext={(orgDetails: OrgDetails): void => {
+ logEvent(NEXT_BUTTON_EVENT_NAME, {
+ currentPageID: 1,
+ nextPageID: 2,
+ });
+
setOrgDetails(orgDetails);
setCurrentStep(2);
}}
@@ -209,8 +237,20 @@ function OnboardingQuestionaire(): JSX.Element {
setCurrentStep(1)}
- onNext={(): void => setCurrentStep(3)}
+ onBack={(): void => {
+ logEvent(BACK_BUTTON_EVENT_NAME, {
+ currentPageID: 2,
+ prevPageID: 1,
+ });
+ setCurrentStep(1);
+ }}
+ onNext={(): void => {
+ logEvent(NEXT_BUTTON_EVENT_NAME, {
+ currentPageID: 2,
+ nextPageID: 3,
+ });
+ setCurrentStep(3);
+ }}
/>
)}
@@ -220,9 +260,15 @@ function OnboardingQuestionaire(): JSX.Element {
isUpdatingProfile={isUpdatingProfile}
optimiseSignozDetails={optimiseSignozDetails}
setOptimiseSignozDetails={setOptimiseSignozDetails}
- onBack={(): void => setCurrentStep(2)}
+ onBack={(): void => {
+ logEvent(BACK_BUTTON_EVENT_NAME, {
+ currentPageID: 3,
+ prevPageID: 2,
+ });
+ setCurrentStep(2);
+ }}
onNext={handleUpdateProfile}
- onWillDoLater={(): void => setCurrentStep(4)} // This is temporary, only to skip gateway api call as it's not setup on staging yet
+ onWillDoLater={(): void => setCurrentStep(4)}
/>
)}
@@ -231,7 +277,13 @@ function OnboardingQuestionaire(): JSX.Element {
isLoading={updatingOrgOnboardingStatus}
teamMembers={teamMembers}
setTeamMembers={setTeamMembers}
- onBack={(): void => setCurrentStep(3)}
+ onBack={(): void => {
+ logEvent(BACK_BUTTON_EVENT_NAME, {
+ currentPageID: 4,
+ prevPageID: 3,
+ });
+ setCurrentStep(3);
+ }}
onNext={handleOnboardingComplete}
/>
)}
diff --git a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
index 36c54aa9a1..60905fa33b 100644
--- a/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
+++ b/frontend/src/container/QueryBuilder/filters/AggregatorFilter/AggregatorFilter.tsx
@@ -81,8 +81,10 @@ export const AggregatorFilter = memo(function AggregatorFilter({
prefix: item.type || '',
condition: !item.isColumn,
}),
+ !item.isColumn && item.type ? item.type : '',
)}
dataType={item.dataType}
+ type={item.type || ''}
/>
),
value: `${item.key}${selectValueDivider}${createIdFromObjectFields(
@@ -187,6 +189,9 @@ export const AggregatorFilter = memo(function AggregatorFilter({
prefix: query.aggregateAttribute.type || '',
condition: !query.aggregateAttribute.isColumn,
}),
+ !query.aggregateAttribute.isColumn && query.aggregateAttribute.type
+ ? query.aggregateAttribute.type
+ : '',
);
return (
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
index bed3870570..476bf71f21 100644
--- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/GroupByFilter.tsx
@@ -75,8 +75,10 @@ export const GroupByFilter = memo(function GroupByFilter({
prefix: item.type || '',
condition: !item.isColumn,
}),
+ !item.isColumn && item.type ? item.type : '',
)}
dataType={item.dataType || ''}
+ type={item.type || ''}
/>
),
value: `${item.id}`,
@@ -166,6 +168,7 @@ export const GroupByFilter = memo(function GroupByFilter({
prefix: item.type || '',
condition: !item.isColumn,
}),
+ !item.isColumn && item.type ? item.type : '',
)}`,
value: `${item.id}`,
}),
diff --git a/frontend/src/container/QueryBuilder/filters/GroupByFilter/utils.ts b/frontend/src/container/QueryBuilder/filters/GroupByFilter/utils.ts
index 50dccec4d9..0fb85a7e30 100644
--- a/frontend/src/container/QueryBuilder/filters/GroupByFilter/utils.ts
+++ b/frontend/src/container/QueryBuilder/filters/GroupByFilter/utils.ts
@@ -1,8 +1,9 @@
import { MetricsType } from 'container/MetricsApplication/constant';
-export function removePrefix(str: string): string {
+export function removePrefix(str: string, type: string): string {
const tagPrefix = `${MetricsType.Tag}_`;
const resourcePrefix = `${MetricsType.Resource}_`;
+ const scopePrefix = `${MetricsType.Scope}_`;
if (str.startsWith(tagPrefix)) {
return str.slice(tagPrefix.length);
@@ -10,5 +11,9 @@ export function removePrefix(str: string): string {
if (str.startsWith(resourcePrefix)) {
return str.slice(resourcePrefix.length);
}
+ if (str.startsWith(scopePrefix) && type === MetricsType.Scope) {
+ return str.slice(scopePrefix.length);
+ }
+
return str;
}
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/OptionRenderer.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/OptionRenderer.tsx
index a7dcef96c3..a93041f5e8 100644
--- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/OptionRenderer.tsx
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/OptionRenderer.tsx
@@ -3,25 +3,23 @@ import './QueryBuilderSearch.styles.scss';
import { Tooltip } from 'antd';
import { TagContainer, TagLabel, TagValue } from './style';
-import { getOptionType } from './utils';
function OptionRenderer({
label,
value,
dataType,
+ type,
}: OptionRendererProps): JSX.Element {
- const optionType = getOptionType(label);
-
return (
- {optionType ? (
+ {type ? (
{value}
Type:
- {optionType}
+ {type}
Data type:
@@ -43,6 +41,7 @@ interface OptionRendererProps {
label: string;
value: string;
dataType: string;
+ type: string;
}
export default OptionRenderer;
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx
index c1f4b85a11..ba30d96d9c 100644
--- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx
@@ -410,6 +410,7 @@ function QueryBuilderSearch({
label={option.label}
value={option.value}
dataType={option.dataType || ''}
+ type={option.type || ''}
/>
{option.selected && }
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.styles.scss b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.styles.scss
index 7aee4f9414..60eec0bdb6 100644
--- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.styles.scss
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2.styles.scss
@@ -260,6 +260,20 @@
background: rgba(189, 153, 121, 0.1);
}
}
+
+ &.scope {
+ border: 1px solid rgba(113, 144, 249, 0.2);
+
+ .ant-typography {
+ color: var(--bg-robin-400);
+ background: rgba(113, 144, 249, 0.1);
+ font-size: 14px;
+ }
+
+ .ant-tag-close-icon {
+ background: rgba(113, 144, 249, 0.1);
+ }
+ }
}
}
}
diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/Suggestions.styles.scss b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/Suggestions.styles.scss
index 1b434316e5..bff02fab3e 100644
--- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/Suggestions.styles.scss
+++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearchV2/Suggestions.styles.scss
@@ -94,6 +94,25 @@
letter-spacing: -0.06px;
}
}
+
+ &.scope {
+ border-radius: 50px;
+ background: rgba(113, 144, 249, 0.1) !important;
+ color: var(--bg-robin-400) !important;
+
+ .dot {
+ background-color: var(--bg-robin-400);
+ }
+ .text {
+ color: var(--bg-robin-400);
+ font-family: Inter;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 18px; /* 150% */
+ letter-spacing: -0.06px;
+ }
+ }
}
}
.option-meta-data-container {
diff --git a/frontend/src/container/QueryBuilder/type.ts b/frontend/src/container/QueryBuilder/type.ts
index 183dd157f8..d20925e330 100644
--- a/frontend/src/container/QueryBuilder/type.ts
+++ b/frontend/src/container/QueryBuilder/type.ts
@@ -16,4 +16,5 @@ export type Option = {
selected?: boolean;
dataType?: string;
isIndexed?: boolean;
+ type?: string;
};
diff --git a/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx b/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx
index 897b0d7e33..13ecd15b8b 100644
--- a/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx
+++ b/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx
@@ -8,15 +8,22 @@ type UseOnboardingStatus = (
options?: UseQueryOptions<
SuccessResponse | ErrorResponse
>,
+ endpointService?: string,
+ queryKey?: string,
) => UseQueryResult | ErrorResponse>;
-export const useOnboardingStatus: UseOnboardingStatus = (options) =>
+export const useOnboardingStatus: UseOnboardingStatus = (
+ options,
+ endpointService,
+ queryKey,
+) =>
useQuery | ErrorResponse>({
- queryKey: ['onboardingStatus'],
+ queryKey: [queryKey || `onboardingStatus-${endpointService}`],
queryFn: () =>
getOnboardingStatus({
start: (Date.now() - 15 * 60 * 1000) * 1_000_000,
end: Date.now() * 1_000_000,
+ endpointService,
}),
...options,
});
diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts
index 2f24dd0d21..e990f789de 100644
--- a/frontend/src/hooks/queryBuilder/useOptions.ts
+++ b/frontend/src/hooks/queryBuilder/useOptions.ts
@@ -46,6 +46,7 @@ export const useOptions = (
value: item.key,
dataType: item.dataType,
isIndexed: item?.isIndexed,
+ type: item?.type || '',
})),
[getLabel],
);
diff --git a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx
index 2f37c4fc9d..00987a0a66 100644
--- a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx
+++ b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/ActionButtons.tsx
@@ -2,82 +2,90 @@ import './ActionButtons.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Divider, Dropdown, MenuProps, Switch, Tooltip } from 'antd';
-import { QueryParams } from 'constants/query';
-import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
-import useUrlQuery from 'hooks/useUrlQuery';
-import history from 'lib/history';
import { Copy, Ellipsis, PenLine, Trash2 } from 'lucide-react';
import {
useAlertRuleDelete,
useAlertRuleDuplicate,
useAlertRuleStatusToggle,
+ useAlertRuleUpdate,
} from 'pages/AlertDetails/hooks';
import CopyToClipboard from 'periscope/components/CopyToClipboard';
import { useAlertRule } from 'providers/Alert';
-import React, { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import { CSSProperties } from 'styled-components';
import { AlertDef } from 'types/api/alerts/def';
import { AlertHeaderProps } from '../AlertHeader';
+import RenameModal from './RenameModal';
const menuItemStyle: CSSProperties = {
fontSize: '14px',
letterSpacing: '0.14px',
};
+
function AlertActionButtons({
ruleId,
alertDetails,
+ setUpdatedName,
}: {
ruleId: string;
alertDetails: AlertHeaderProps['alertDetails'];
+ setUpdatedName: (name: string) => void;
}): JSX.Element {
const { alertRuleState, setAlertRuleState } = useAlertRule();
- const { handleAlertStateToggle } = useAlertRuleStatusToggle({ ruleId });
+ const [intermediateName, setIntermediateName] = useState(
+ alertDetails.alert,
+ );
+ const [isRenameAlertOpen, setIsRenameAlertOpen] = useState(false);
+ const isDarkMode = useIsDarkMode();
+ const { handleAlertStateToggle } = useAlertRuleStatusToggle({ ruleId });
const { handleAlertDuplicate } = useAlertRuleDuplicate({
alertDetails: (alertDetails as unknown) as AlertDef,
});
const { handleAlertDelete } = useAlertRuleDelete({ ruleId: Number(ruleId) });
+ const { handleAlertUpdate, isLoading } = useAlertRuleUpdate({
+ alertDetails: (alertDetails as unknown) as AlertDef,
+ setUpdatedName,
+ intermediateName,
+ });
- const params = useUrlQuery();
-
- const handleRename = React.useCallback(() => {
- params.set(QueryParams.ruleId, String(ruleId));
- history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
- }, [params, ruleId]);
-
- const menu: MenuProps['items'] = React.useMemo(
- () => [
- {
- key: 'rename-rule',
- label: 'Rename',
- icon: ,
- onClick: (): void => handleRename(),
- style: menuItemStyle,
- },
- {
- key: 'duplicate-rule',
- label: 'Duplicate',
- icon: ,
- onClick: (): void => handleAlertDuplicate(),
- style: menuItemStyle,
- },
- { type: 'divider' },
- {
- key: 'delete-rule',
- label: 'Delete',
- icon: ,
- onClick: (): void => handleAlertDelete(),
- style: {
- ...menuItemStyle,
- color: Color.BG_CHERRY_400,
- },
+ const handleRename = useCallback(() => {
+ setIsRenameAlertOpen(true);
+ }, []);
+
+ const onNameChangeHandler = useCallback(() => {
+ handleAlertUpdate();
+ setIsRenameAlertOpen(false);
+ }, [handleAlertUpdate]);
+
+ const menuItems: MenuProps['items'] = [
+ {
+ key: 'rename-rule',
+ label: 'Rename',
+ icon: ,
+ onClick: handleRename,
+ style: menuItemStyle,
+ },
+ {
+ key: 'duplicate-rule',
+ label: 'Duplicate',
+ icon: ,
+ onClick: handleAlertDuplicate,
+ style: menuItemStyle,
+ },
+ {
+ key: 'delete-rule',
+ label: 'Delete',
+ icon: ,
+ onClick: handleAlertDelete,
+ style: {
+ ...menuItemStyle,
+ color: Color.BG_CHERRY_400,
},
- ],
- [handleAlertDelete, handleAlertDuplicate, handleRename],
- );
- const isDarkMode = useIsDarkMode();
+ },
+ ];
// state for immediate UI feedback rather than waiting for onSuccess of handleAlertStateTiggle to updating the alertRuleState
const [isAlertRuleDisabled, setIsAlertRuleDisabled] = useState<
@@ -95,35 +103,48 @@ function AlertActionButtons({
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => (): void => setAlertRuleState(undefined), []);
+ const toggleAlertRule = useCallback(() => {
+ setIsAlertRuleDisabled((prev) => !prev);
+ handleAlertStateToggle();
+ }, [handleAlertStateToggle]);
+
return (
-
-
- {isAlertRuleDisabled !== undefined && (
- {
- setIsAlertRuleDisabled((prev) => !prev);
- handleAlertStateToggle();
- }}
- checked={!isAlertRuleDisabled}
- />
- )}
-
-
-
-
-
-
-
-
+ <>
+
+
+ {isAlertRuleDisabled !== undefined && (
+
+ )}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ >
);
}
diff --git a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.styles.scss b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.styles.scss
new file mode 100644
index 0000000000..d3552d8143
--- /dev/null
+++ b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.styles.scss
@@ -0,0 +1,138 @@
+.rename-alert {
+ .ant-modal-content {
+ width: 384px;
+ flex-shrink: 0;
+ border-radius: 4px;
+ border: 1px solid var(--bg-slate-500);
+ background: var(--bg-ink-400);
+ box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
+ padding: 0px;
+
+ .ant-modal-header {
+ height: 52px;
+ padding: 16px;
+ background: var(--bg-ink-400);
+ border-bottom: 1px solid var(--bg-slate-500);
+ margin-bottom: 0px;
+ .ant-modal-title {
+ color: var(--bg-vanilla-100);
+ font-family: Inter;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 20px; /* 142.857% */
+ width: 349px;
+ height: 20px;
+ }
+ }
+
+ .ant-modal-body {
+ padding: 16px;
+
+ .alert-content {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+
+ .name-text {
+ color: var(--bg-vanilla-100);
+ font-family: Inter;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 500;
+ line-height: 20px; /* 142.857% */
+ }
+
+ .alert-name-input {
+ display: flex;
+ padding: 6px 6px 6px 8px;
+ align-items: center;
+ gap: 4px;
+ align-self: stretch;
+ border-radius: 0px 2px 2px 0px;
+ border: 1px solid var(--bg-slate-400);
+ background: var(--bg-ink-300);
+ }
+ }
+ }
+
+ .ant-modal-footer {
+ padding: 16px;
+ margin-top: 0px;
+ .alert-rename {
+ display: flex;
+ flex-direction: row-reverse;
+ gap: 12px;
+
+ .cancel-btn {
+ display: flex;
+ padding: 4px 8px;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+ border-radius: 2px;
+ background: var(--bg-slate-500);
+
+ .ant-btn-icon {
+ margin-inline-end: 0px;
+ }
+ }
+
+ .rename-btn {
+ display: flex;
+ align-items: center;
+ display: flex;
+ padding: 4px 8px;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+ border-radius: 2px;
+ background: var(--bg-robin-500);
+
+ .ant-btn-icon {
+ margin-inline-end: 0px;
+ }
+ }
+ }
+ }
+ }
+}
+
+.lightMode {
+ .rename-alert {
+ .ant-modal-content {
+ border: 1px solid var(--bg-vanilla-300);
+ background: var(--bg-vanilla-100);
+
+ .ant-modal-header {
+ background: var(--bg-vanilla-100);
+ border-bottom: 1px solid var(--bg-vanilla-300);
+
+ .ant-modal-title {
+ color: var(--bg-ink-300);
+ }
+ }
+
+ .ant-modal-body {
+ .alert-content {
+ .name-text {
+ color: var(--bg-ink-300);
+ }
+
+ .alert-name-input {
+ border: 1px solid var(--bg-vanilla-300);
+ background: var(--bg-vanilla-100);
+ }
+ }
+ }
+
+ .ant-modal-footer {
+ .alert-rename {
+ .cancel-btn {
+ background: var(--bg-vanilla-300);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.tsx b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.tsx
new file mode 100644
index 0000000000..ce73260fb3
--- /dev/null
+++ b/frontend/src/pages/AlertDetails/AlertHeader/ActionButtons/RenameModal.tsx
@@ -0,0 +1,95 @@
+import './RenameModal.styles.scss';
+
+import { Button, Input, InputRef, Modal, Typography } from 'antd';
+import { Check, X } from 'lucide-react';
+import { useCallback, useEffect, useRef } from 'react';
+
+type Props = {
+ isOpen: boolean;
+ setIsOpen: (isOpen: boolean) => void;
+ onNameChangeHandler: () => void;
+ isLoading: boolean;
+ intermediateName: string;
+ setIntermediateName: (name: string) => void;
+};
+
+function RenameModal({
+ isOpen,
+ setIsOpen,
+ onNameChangeHandler,
+ isLoading,
+ intermediateName,
+ setIntermediateName,
+}: Props): JSX.Element {
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (isOpen && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isOpen]);
+
+ const handleClose = useCallback((): void => setIsOpen(false), [setIsOpen]);
+
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent): void => {
+ if (isOpen) {
+ if (e.key === 'Enter') {
+ onNameChangeHandler();
+ } else if (e.key === 'Escape') {
+ handleClose();
+ }
+ }
+ };
+
+ document.addEventListener('keydown', handleKeyDown);
+
+ return (): void => {
+ document.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [isOpen, onNameChangeHandler, handleClose]);
+
+ return (
+
+ }
+ className="rename-btn"
+ onClick={onNameChangeHandler}
+ disabled={isLoading}
+ >
+ Rename Alert
+
+ }
+ className="cancel-btn"
+ onClick={handleClose}
+ >
+ Cancel
+
+
+ }
+ >
+
+ Enter a new name
+ setIntermediateName(e.target.value)}
+ />
+
+
+ );
+}
+
+export default RenameModal;
diff --git a/frontend/src/pages/AlertDetails/AlertHeader/AlertHeader.tsx b/frontend/src/pages/AlertDetails/AlertHeader/AlertHeader.tsx
index 04edd6a8b0..f617a6d78e 100644
--- a/frontend/src/pages/AlertDetails/AlertHeader/AlertHeader.tsx
+++ b/frontend/src/pages/AlertDetails/AlertHeader/AlertHeader.tsx
@@ -2,7 +2,7 @@ import './AlertHeader.styles.scss';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
import { useAlertRule } from 'providers/Alert';
-import { useMemo } from 'react';
+import { useMemo, useState } from 'react';
import AlertActionButtons from './ActionButtons/ActionButtons';
import AlertLabels from './AlertLabels/AlertLabels';
@@ -19,7 +19,9 @@ export type AlertHeaderProps = {
};
};
function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
- const { state, alert, labels } = alertDetails;
+ const { state, alert: alertName, labels } = alertDetails;
+ const { alertRuleState } = useAlertRule();
+ const [updatedName, setUpdatedName] = useState(alertName);
const labelsWithoutSeverity = useMemo(
() =>
@@ -29,8 +31,6 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
[labels],
);
- const { alertRuleState } = useAlertRule();
-
return (
@@ -38,7 +38,7 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
@@ -54,7 +54,11 @@ function AlertHeader({ alertDetails }: AlertHeaderProps): JSX.Element {
);
diff --git a/frontend/src/pages/AlertDetails/hooks.tsx b/frontend/src/pages/AlertDetails/hooks.tsx
index 8a630a6374..c159d2169b 100644
--- a/frontend/src/pages/AlertDetails/hooks.tsx
+++ b/frontend/src/pages/AlertDetails/hooks.tsx
@@ -57,8 +57,11 @@ export const useAlertHistoryQueryParams = (): {
const startTime = params.get(QueryParams.startTime);
const endTime = params.get(QueryParams.endTime);
+ const relativeTimeParam = params.get(QueryParams.relativeTime);
+
const relativeTime =
- params.get(QueryParams.relativeTime) ?? RelativeTimeMap['6hr'];
+ (relativeTimeParam === 'null' ? null : relativeTimeParam) ??
+ RelativeTimeMap['6hr'];
const intStartTime = parseInt(startTime || '0', 10);
const intEndTime = parseInt(endTime || '0', 10);
@@ -464,6 +467,44 @@ export const useAlertRuleDuplicate = ({
return { handleAlertDuplicate };
};
+export const useAlertRuleUpdate = ({
+ alertDetails,
+ setUpdatedName,
+ intermediateName,
+}: {
+ alertDetails: AlertDef;
+ setUpdatedName: (name: string) => void;
+ intermediateName: string;
+}): {
+ handleAlertUpdate: () => void;
+ isLoading: boolean;
+} => {
+ const { notifications } = useNotifications();
+ const handleError = useAxiosError();
+
+ const { mutate: updateAlertRule, isLoading } = useMutation(
+ [REACT_QUERY_KEY.UPDATE_ALERT_RULE, alertDetails.id],
+ save,
+ {
+ onMutate: () => setUpdatedName(intermediateName),
+ onSuccess: () =>
+ notifications.success({ message: 'Alert renamed successfully' }),
+ onError: (error) => {
+ setUpdatedName(alertDetails.alert);
+ handleError(error);
+ },
+ },
+ );
+
+ const handleAlertUpdate = (): void => {
+ updateAlertRule({
+ data: { ...alertDetails, alert: intermediateName },
+ id: alertDetails.id,
+ });
+ };
+
+ return { handleAlertUpdate, isLoading };
+};
export const useAlertRuleDelete = ({
ruleId,
diff --git a/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx
index 931502b8e1..5793d40b7b 100644
--- a/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx
+++ b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx
@@ -1,9 +1,12 @@
+/* eslint-disable no-nested-ternary */
import '../MessagingQueues.styles.scss';
import { Select, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
+import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
+import useUrlQuery from 'hooks/useUrlQuery';
import { ListMinus } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
@@ -13,8 +16,9 @@ import {
MessagingQueuesViewTypeOptions,
ProducerLatencyOptions,
} from '../MessagingQueuesUtils';
-import { SelectLabelWithComingSoon } from '../MQCommon/MQCommon';
+import DropRateView from '../MQDetails/DropRateView/DropRateView';
import MessagingQueueOverview from '../MQDetails/MessagingQueueOverview';
+import MetricPage from '../MQDetails/MetricPage/MetricPage';
import MessagingQueuesDetails from '../MQDetails/MQDetails';
import MessagingQueuesConfigOptions from '../MQGraph/MQConfigOptions';
import MessagingQueuesGraph from '../MQGraph/MQGraph';
@@ -33,10 +37,34 @@ function MQDetailPage(): JSX.Element {
setproducerLatencyOption,
] = useState