From bdcae62bf98e260dbafb171c7b8678ea27078816 Mon Sep 17 00:00:00 2001 From: Ekansh Gupta Date: Mon, 28 Oct 2024 22:32:04 +0530 Subject: [PATCH 01/52] fix: fixed the step interval which was being perculated to list view (#6260) * fix: fixed the step interval which was being perculated to list view * fix: fixed the step interval which was being perculated to list view * fix: fixed the step interval which was being perculated to list view * fix: fixed the step interval which was being perculated to list view * fix: fixed the step interval which was being perculated to list view * fix: fixed the step interval which was being perculated to list view * chore: bump signoz-otel-collector version (#6290) * Chore: bump signoz otel collector dependency to 0.111.5 (#6302) * chore: bump signoz-otel-collector dependency version to 0.111.5 * chore: logs filter suggestions: update import for ResourceHierarchy from signoz-otel-collector * fix: fixed the step interval which was being perculated to list view --------- Co-authored-by: Srikanth Chekuri Co-authored-by: Raj Kamal Singh <1133322+raj-k-singh@users.noreply.github.com> --- pkg/query-service/app/querier/querier_test.go | 2 +- pkg/query-service/app/querier/v2/querier_test.go | 2 +- pkg/query-service/app/traces/v3/query_builder.go | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/query-service/app/querier/querier_test.go b/pkg/query-service/app/querier/querier_test.go index c30841546e..6a28c2c267 100644 --- a/pkg/query-service/app/querier/querier_test.go +++ b/pkg/query-service/app/querier/querier_test.go @@ -753,7 +753,7 @@ func TestQueryRangeValueType(t *testing.T) { // No caching expectedTimeRangeInQueryString := []string{ fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", 1675115520000, 1675115580000+120*60*1000), - fmt.Sprintf("timestamp >= '%d' AND timestamp <= '%d'", (1675115580000+60*60*1000)*int64(1000000), (1675115580000+180*60*1000)*int64(1000000)), + fmt.Sprintf("timestamp >= '%d' AND timestamp <= '%d'", 1675119196722000000, 1675126396722000000), } for i, param := range params { diff --git a/pkg/query-service/app/querier/v2/querier_test.go b/pkg/query-service/app/querier/v2/querier_test.go index 6dfc921183..62e18d244d 100644 --- a/pkg/query-service/app/querier/v2/querier_test.go +++ b/pkg/query-service/app/querier/v2/querier_test.go @@ -800,7 +800,7 @@ func TestV2QueryRangeValueType(t *testing.T) { expectedTimeRangeInQueryString := []string{ fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", 1675115520000, 1675115580000+120*60*1000), // 31st Jan, 03:23:00 to 31st Jan, 05:23:00 fmt.Sprintf("unix_milli >= %d AND unix_milli < %d", 1675115580000+120*60*1000, 1675115580000+180*60*1000), // 31st Jan, 05:23:00 to 31st Jan, 06:23:00 - fmt.Sprintf("timestamp >= '%d' AND timestamp <= '%d'", (1675115580000+60*60*1000)*int64(1000000), (1675115580000+180*60*1000)*int64(1000000)), // 31st Jan, 05:23:00 to 31st Jan, 06:23:00 + fmt.Sprintf("timestamp >= '%d' AND timestamp <= '%d'", (1675119196722)*int64(1000000), (1675126396722)*int64(1000000)), // 31st Jan, 05:23:00 to 31st Jan, 06:23:00 } for i, param := range params { diff --git a/pkg/query-service/app/traces/v3/query_builder.go b/pkg/query-service/app/traces/v3/query_builder.go index ce9c54573c..ad5b69229b 100644 --- a/pkg/query-service/app/traces/v3/query_builder.go +++ b/pkg/query-service/app/traces/v3/query_builder.go @@ -501,8 +501,12 @@ func addOffsetToQuery(query string, offset uint64) string { // step is in seconds func PrepareTracesQuery(start, end int64, panelType v3.PanelType, mq *v3.BuilderQuery, options v3.QBOptions) (string, error) { // adjust the start and end time to the step interval - start = start - (start % (mq.StepInterval * 1000)) - end = end - (end % (mq.StepInterval * 1000)) + if panelType == v3.PanelTypeGraph { + // adjust the start and end time to the step interval for graph panel types + start = start - (start % (mq.StepInterval * 1000)) + end = end - (end % (mq.StepInterval * 1000)) + } + if options.GraphLimitQtype == constants.FirstQueryGraphLimit { // give me just the group by names query, err := buildTracesQuery(start, end, mq.StepInterval, mq, constants.SIGNOZ_SPAN_INDEX_TABLENAME, panelType, options) From cc90321ac03ef49a9051918524723f0ef2313ff5 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Tue, 29 Oct 2024 13:18:34 +0530 Subject: [PATCH 02/52] chore: move hostname to resource attributes for logs qf (#6303) * chore: move hostname to resource attributes for logs qf * chore: fix the key for hostname --- frontend/src/pages/LogsExplorer/utils.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/LogsExplorer/utils.tsx b/frontend/src/pages/LogsExplorer/utils.tsx index 5b5ef631b0..f49aec6923 100644 --- a/frontend/src/pages/LogsExplorer/utils.tsx +++ b/frontend/src/pages/LogsExplorer/utils.tsx @@ -66,9 +66,9 @@ export const LogsQuickFiltersConfig: IQuickFiltersConfig[] = [ type: FiltersType.CHECKBOX, title: 'Hostname', attributeKey: { - key: 'hostname', + key: 'host.name', dataType: DataTypes.String, - type: 'tag', + type: 'resource', isColumn: false, isJSON: false, }, From 68d25a89894e141f208b62ca3c2c220a37cbc013 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 30 Oct 2024 14:12:45 +0530 Subject: [PATCH 03/52] fix: add support for {{.Labels.}} with dots in `key` for template (#6282) --- pkg/query-service/rules/templates.go | 22 +++++++++++++++++----- pkg/query-service/rules/templates_test.go | 11 +++++++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/pkg/query-service/rules/templates.go b/pkg/query-service/rules/templates.go index 519ff3859b..b5487011ce 100644 --- a/pkg/query-service/rules/templates.go +++ b/pkg/query-service/rules/templates.go @@ -234,15 +234,27 @@ func AlertTemplateData(labels map[string]string, value string, threshold string) // If there is a go template block, it won't be replaced. // The example for existing go template block is: {{$threshold}} or {{$value}} or any other valid go template syntax. func (te *TemplateExpander) preprocessTemplate() { - re := regexp.MustCompile(`({{.*?}})|(\$(\w+(?:\.\w+)*))`) - te.text = re.ReplaceAllStringFunc(te.text, func(match string) string { + // Handle the $variable syntax + reDollar := regexp.MustCompile(`({{.*?}})|(\$(\w+(?:\.\w+)*))`) + te.text = reDollar.ReplaceAllStringFunc(te.text, func(match string) string { if strings.HasPrefix(match, "{{") { // If it's a Go template block, leave it unchanged return match } - // Otherwise, it's our custom $variable syntax - path := strings.Split(match[1:], ".") - return "{{index $labels \"" + strings.Join(path, ".") + "\"}}" + path := match[1:] // Remove the '$' + return fmt.Sprintf(`{{index $labels "%s"}}`, path) + }) + + // Handle the {{.Labels.service.name}} syntax + reLabels := regexp.MustCompile(`{{\s*\.Labels\.([a-zA-Z0-9_.]+)(.*?)}}`) + te.text = reLabels.ReplaceAllStringFunc(te.text, func(match string) string { + submatches := reLabels.FindStringSubmatch(match) + if len(submatches) < 3 { + return match // Should not happen + } + path := submatches[1] + rest := submatches[2] + return fmt.Sprintf(`{{index .Labels "%s"%s}}`, path, rest) }) } diff --git a/pkg/query-service/rules/templates_test.go b/pkg/query-service/rules/templates_test.go index 737a3d20ca..66d958e8f3 100644 --- a/pkg/query-service/rules/templates_test.go +++ b/pkg/query-service/rules/templates_test.go @@ -63,3 +63,14 @@ func TestTemplateExpander_WithMissingKey(t *testing.T) { } require.Equal(t, "test exceeds 100 and observed at 200", result) } + +func TestTemplateExpander_WithLablesDotSyntax(t *testing.T) { + defs := "{{$labels := .Labels}}{{$value := .Value}}{{$threshold := .Threshold}}" + data := AlertTemplateData(map[string]string{"service.name": "my-service"}, "200", "100") + expander := NewTemplateExpander(context.Background(), defs+"test {{.Labels.service.name}} exceeds {{$threshold}} and observed at {{$value}}", "test", data, times.Time(time.Now().Unix()), nil) + result, err := expander.Expand() + if err != nil { + t.Fatal(err) + } + require.Equal(t, "test my-service exceeds 100 and observed at 200", result) +} From 829e1f0920e9ba977878c759a7f23a107dca5657 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 2 Sep 2024 23:52:12 +0530 Subject: [PATCH 04/52] feat: onboarding v2 base setup --- frontend/src/AppRoutes/pageComponents.ts | 4 ++++ frontend/src/AppRoutes/routes.ts | 8 ++++++++ frontend/src/constants/routes.ts | 1 + frontend/src/container/AppLayout/index.tsx | 2 ++ frontend/src/container/SideNav/config.ts | 1 + frontend/src/container/SideNav/menuItems.tsx | 11 +++++++++++ frontend/src/container/TopNav/Breadcrumbs/index.tsx | 1 + .../src/pages/OnboardingPageV2/OnboardingPageV2.tsx | 11 +++++++++++ frontend/src/pages/OnboardingPageV2/index.tsx | 3 +++ frontend/src/utils/permission/index.ts | 1 + 10 files changed, 43 insertions(+) create mode 100644 frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx create mode 100644 frontend/src/pages/OnboardingPageV2/index.tsx diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 0a7764149b..c267304e6b 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -66,6 +66,10 @@ export const Onboarding = Loadable( () => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'), ); +export const OnboardingV2 = Loadable( + () => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPageV2'), +); + export const DashboardPage = Loadable( () => import(/* webpackChunkName: "DashboardPage" */ 'pages/DashboardsListPage'), diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 42ce00c0fb..fc59fef1e3 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -31,6 +31,7 @@ import { NewDashboardPage, OldLogsExplorer, Onboarding, + OnboardingV2, OrganizationSettings, PasswordReset, PipelinePage, @@ -68,6 +69,13 @@ const routes: AppRoutes[] = [ isPrivate: true, key: 'GET_STARTED', }, + { + path: ROUTES.GET_STARTED_V2, + exact: false, + component: OnboardingV2, + isPrivate: true, + key: 'GET_STARTED_V2', + }, { component: LogsIndexToFields, path: ROUTES.LOGS_INDEX_FIELDS, diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index b4f43ee684..50d189f4a8 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -8,6 +8,7 @@ const ROUTES = { TRACE_DETAIL: '/trace/:id', TRACES_EXPLORER: '/traces-explorer', GET_STARTED: '/get-started', + GET_STARTED_V2: '/get-started-v2', GET_STARTED_APPLICATION_MONITORING: '/get-started/application-monitoring', GET_STARTED_LOGS_MANAGEMENT: '/get-started/logs-management', GET_STARTED_INFRASTRUCTURE_MONITORING: diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index beb4cea61c..898481e2a5 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -191,6 +191,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const pageTitle = t(routeKey); const renderFullScreen = pathname === ROUTES.GET_STARTED || + pathname === ROUTES.GET_STARTED_V2 || + pathname === ROUTES.WORKSPACE_LOCKED || pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING || pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING || pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT || diff --git a/frontend/src/container/SideNav/config.ts b/frontend/src/container/SideNav/config.ts index 37e9db4d9f..7b37f64bb7 100644 --- a/frontend/src/container/SideNav/config.ts +++ b/frontend/src/container/SideNav/config.ts @@ -27,6 +27,7 @@ export const routeConfig: Record = { [ROUTES.ERROR_DETAIL]: [QueryParams.resourceAttributes], [ROUTES.HOME_PAGE]: [QueryParams.resourceAttributes], [ROUTES.GET_STARTED]: [QueryParams.resourceAttributes], + [ROUTES.GET_STARTED_V2]: [QueryParams.resourceAttributes], [ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes], [ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes], [ROUTES.LOGIN]: [QueryParams.resourceAttributes], diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index 6d24b74c53..a1c41ddea8 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -29,6 +29,12 @@ export const getStartedMenuItem = { icon: , }; +export const getStartedV2MenuItem = { + key: ROUTES.GET_STARTED_V2, + label: 'Get Started V2', + icon: , +}; + export const inviteMemberMenuItem = { key: `${ROUTES.ORG_SETTINGS}#invite-team-members`, label: 'Invite Team Member', @@ -66,6 +72,11 @@ export const trySignozCloudMenuItem: SidebarItem = { }; const menuItems: SidebarItem[] = [ + { + key: ROUTES.GET_STARTED_V2, + label: 'Get Started V2', + icon: , + }, { key: ROUTES.APPLICATION, label: 'Services', diff --git a/frontend/src/container/TopNav/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx index 9efd50d2c3..c98f4d05e2 100644 --- a/frontend/src/container/TopNav/Breadcrumbs/index.tsx +++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx @@ -9,6 +9,7 @@ const breadcrumbNameMap: Record = { [ROUTES.SERVICE_MAP]: 'Service Map', [ROUTES.USAGE_EXPLORER]: 'Usage Explorer', [ROUTES.GET_STARTED]: 'Get Started', + [ROUTES.GET_STARTED_V2]: 'Get Started V2', [ROUTES.ALL_CHANNELS]: 'Channels', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', diff --git a/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx b/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx new file mode 100644 index 0000000000..ccdc0226f8 --- /dev/null +++ b/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx @@ -0,0 +1,11 @@ +import { Typography } from 'antd'; + +function OnboardingPageV2(): JSX.Element { + return ( +
+ Onboarding V2 +
+ ); +} + +export default OnboardingPageV2; diff --git a/frontend/src/pages/OnboardingPageV2/index.tsx b/frontend/src/pages/OnboardingPageV2/index.tsx new file mode 100644 index 0000000000..9fad953dfe --- /dev/null +++ b/frontend/src/pages/OnboardingPageV2/index.tsx @@ -0,0 +1,3 @@ +import OnboardingPage from './OnboardingPageV2'; + +export default OnboardingPage; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 8a35121f57..6052900a05 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -86,6 +86,7 @@ export const routePermission: Record = { LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'], TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'], + GET_STARTED_V2: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_APPLICATION_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_INFRASTRUCTURE_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_LOGS_MANAGEMENT: ['ADMIN', 'EDITOR', 'VIEWER'], From 438cbcef87e9ef727f249ad7ba531955b1497708 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 18 Sep 2024 13:10:56 +0530 Subject: [PATCH 05/52] feat: add questionaire components (#5998) * feat: add questionaire components * feat: update css * feat: delete icon svgs and update css * feat: update component names --- frontend/public/Logos/hippa.svg | 10 + .../public/Logos/signoz-brand-logo-new.svg | 30 ++ frontend/public/Logos/soc2.svg | 72 +++++ frontend/public/fonts/Satoshi-Regular.woff2 | Bin 0 -> 25516 bytes .../container/AppLayout/AppLayout.styles.scss | 2 + .../AboutSigNozQuestions.tsx | 202 +++++++++++++ .../InviteTeamMembers/InviteTeamMembers.tsx | 82 ++++++ .../OnboardingFooter.styles.scss | 46 +++ .../OnboardingFooter/OnboardingFooter.tsx | 55 ++++ .../OnboardingFooter/index.ts | 1 + .../OnboardingHeader.styles.scss | 58 ++++ .../OnboardingHeader/OnboardingHeader.tsx | 19 ++ .../OnboardingHeader/index.ts | 1 + .../OnboardingQuestionaire.styles.scss | 278 ++++++++++++++++++ .../OptimiseSignozNeeds.tsx | 127 ++++++++ .../OrgQuestions/OrgQuestions.styles.scss | 0 .../OrgQuestions/OrgQuestions.tsx | 257 ++++++++++++++++ .../OnboardingQuestionaire/index.tsx | 55 ++++ .../OnboardingPageV2/OnboardingPageV2.tsx | 4 +- 19 files changed, 1297 insertions(+), 2 deletions(-) create mode 100644 frontend/public/Logos/hippa.svg create mode 100644 frontend/public/Logos/signoz-brand-logo-new.svg create mode 100644 frontend/public/Logos/soc2.svg create mode 100644 frontend/public/fonts/Satoshi-Regular.woff2 create mode 100644 frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.styles.scss create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingFooter/index.ts create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingHeader/index.ts create mode 100644 frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss create mode 100644 frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.styles.scss create mode 100644 frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx create mode 100644 frontend/src/container/OnboardingQuestionaire/index.tsx diff --git a/frontend/public/Logos/hippa.svg b/frontend/public/Logos/hippa.svg new file mode 100644 index 0000000000..2a1b74b818 --- /dev/null +++ b/frontend/public/Logos/hippa.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/Logos/signoz-brand-logo-new.svg b/frontend/public/Logos/signoz-brand-logo-new.svg new file mode 100644 index 0000000000..f57d3c5def --- /dev/null +++ b/frontend/public/Logos/signoz-brand-logo-new.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/Logos/soc2.svg b/frontend/public/Logos/soc2.svg new file mode 100644 index 0000000000..234321c95c --- /dev/null +++ b/frontend/public/Logos/soc2.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/public/fonts/Satoshi-Regular.woff2 b/frontend/public/fonts/Satoshi-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..81c40ab08a87d975b0e61e0d8497fe4dd3c11da4 GIT binary patch literal 25516 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 literal 0 HcmV?d00001 diff --git a/frontend/src/container/AppLayout/AppLayout.styles.scss b/frontend/src/container/AppLayout/AppLayout.styles.scss index a991f08351..98ca9084f2 100644 --- a/frontend/src/container/AppLayout/AppLayout.styles.scss +++ b/frontend/src/container/AppLayout/AppLayout.styles.scss @@ -7,6 +7,8 @@ width: calc(100% - 64px); z-index: 0; + margin: 0 auto; + .content-container { position: relative; margin: 0 1rem; diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx new file mode 100644 index 0000000000..09ca867e5f --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -0,0 +1,202 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +import '../OnboardingQuestionaire.styles.scss'; + +import { Button, Typography } from 'antd'; +import { ArrowLeft, ArrowRight } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +interface AboutSigNozQuestionsProps { + onNext: () => void; + onBack: () => void; +} + +export function AboutSigNozQuestions({ + onNext, + onBack, +}: AboutSigNozQuestionsProps): JSX.Element { + const [hearAboutSignoz, setHearAboutSignoz] = useState(null); + const [otherAboutSignoz, setOtherAboutSignoz] = useState(''); + const [interestedSignoz, setInterestedSignoz] = useState(null); + const [otherInterest, setOtherInterest] = useState(''); + const [isNextDisabled, setIsNextDisabled] = useState(true); + + useEffect((): void => { + if ( + hearAboutSignoz !== null && + (hearAboutSignoz !== 'Others' || otherAboutSignoz !== '') && + interestedSignoz !== null && + (interestedSignoz !== 'Others' || otherInterest !== '') + ) { + setIsNextDisabled(false); + } else { + setIsNextDisabled(true); + } + }, [hearAboutSignoz, otherAboutSignoz, interestedSignoz, otherInterest]); + + return ( +
+ + Tell Us About Your Interest in SigNoz + + + We'd love to know a little bit about you and your interest in SigNoz + + +
+
+
+
Where did you hear about SigNoz?
+
+ + + + + + + + {hearAboutSignoz === 'Others' ? ( + setOtherAboutSignoz(e.target.value)} + /> + ) : ( + + )} +
+
+ +
+
+ What are you interested in doing with SigNoz? +
+
+ + + + + {interestedSignoz === 'Others' ? ( + setOtherInterest(e.target.value)} + /> + ) : ( + + )} +
+
+
+ +
+ + + +
+
+
+ ); +} diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx new file mode 100644 index 0000000000..e531e586a6 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -0,0 +1,82 @@ +import { Button, Input, Select, Typography } from 'antd'; +import { ArrowLeft, ArrowRight } from 'lucide-react'; + +interface InviteTeamMembersProps { + onNext: () => void; + onBack: () => void; +} + +const userRolesOptions = ( + +); + +function InviteTeamMembers({ + onNext, + onBack, +}: InviteTeamMembersProps): JSX.Element { + return ( +
+ + Observability made collaborative + + + The more your team uses SigNoz, the stronger your observability. Share + dashboards, collaborate on alerts, and troubleshoot faster together. + + +
+
+
+
+ Collaborate with your team +
+ Invite your team to the SigNoz workspace +
+
+ +
+ + + + + +
+
+
+ +
+ + + +
+ +
+ +
+
+
+ ); +} + +export default InviteTeamMembers; diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.styles.scss new file mode 100644 index 0000000000..beddf2441e --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.styles.scss @@ -0,0 +1,46 @@ +.footer-main-container { + display: flex; + justify-content: center; + box-sizing: border-box; +} + +.footer-container { + display: inline-flex; + height: 36px; + padding: 12px; + justify-content: center; + align-items: center; + gap: 32px; + flex-shrink: 0; + border-radius: 4px; + border: 1px solid var(--Greyscale-Slate-500, #161922); + background: var(--Ink-400, #121317); +} + +.footer-container .footer-content { + display: flex; + align-items: center; + gap: 10px; +} + +.footer-container .footer-link { + display: flex; + align-items: center; + gap: 6px; + color: #c0c1c3; +} + +.footer-container .footer-text { + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: 0.2px; +} + +.footer-container .footer-dot { + width: 4px; + height: 4px; + fill: var(--Greyscale-Slate-200, #2c3140); +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.tsx b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.tsx new file mode 100644 index 0000000000..25482e2436 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/OnboardingFooter.tsx @@ -0,0 +1,55 @@ +import './OnboardingFooter.styles.scss'; + +import { ArrowUpRight, Dot } from 'lucide-react'; + +export function OnboardingFooter(): JSX.Element { + return ( +
+ +
+ ); +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/index.ts b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/index.ts new file mode 100644 index 0000000000..cc029cb359 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingFooter/index.ts @@ -0,0 +1 @@ +export { OnboardingFooter } from './OnboardingFooter'; diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss new file mode 100644 index 0000000000..0b2cecaac6 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss @@ -0,0 +1,58 @@ +.header-container { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 0px; + + box-sizing: border-box; +} + +.header-container .logo-container { + display: flex; + align-items: center; + gap: 10px; +} + +.header-container .logo-container img { + height: 17.5px; + width: 17.5px; +} + +.header-container .logo-text { + font-family: 'Satoshi', sans-serif; + color: #fff; + font-size: 15.4px; + font-style: normal; + font-weight: 500; + line-height: 17.5px; +} + +.header-container .get-help-container { + display: flex; + width: 113px; + height: 32px; + padding: 6px; + justify-content: center; + align-items: center; + gap: 6px; + flex-shrink: 0; + border-radius: 2px; + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + background: var(--Ink-300, #16181d); +} + +.header-container .get-help-container img { + width: 12px; + height: 12px; + flex-shrink: 0; +} + +.header-container .get-help-text { + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 10px; + letter-spacing: 0.12px; +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx new file mode 100644 index 0000000000..76801d7620 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx @@ -0,0 +1,19 @@ +import './OnboardingHeader.styles.scss'; + +import { Color } from '@signozhq/design-tokens'; +import { LifeBuoy } from 'lucide-react'; + +export function OnboardingHeader(): JSX.Element { + return ( +
+
+ SigNoz + SigNoz +
+
+ + Get Help +
+
+ ); +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/index.ts b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/index.ts new file mode 100644 index 0000000000..644a6d9b84 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/index.ts @@ -0,0 +1 @@ +export { OnboardingHeader } from './OnboardingHeader'; diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss new file mode 100644 index 0000000000..df07b47f27 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -0,0 +1,278 @@ +.onboarding-questionaire-container { + width: 100%; + display: flex; + margin: 0 auto; + align-items: center; + flex-direction: column; + height: 100vh; + max-width: 1176px; + + .onboarding-questionaire-header { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + + height: 56px; + } + + .onboarding-questionaire-content { + height: calc(100vh - 56px - 60px); + width: 100%; + display: flex; + flex-direction: column; + overflow-y: auto; + + .questions-container { + color: var(--Vanilla-100, #fff); + font-family: Inter; + font-size: 24px; + font-style: normal; + font-weight: 600; + line-height: 32px; + max-width: 600px; + margin: 0 auto; + border-radius: 8px; + max-height: 100%; + } + + .title { + color: var(--Vanilla-100, #fff) !important; + font-size: 24px !important; + line-height: 32px !important; + margin-bottom: 8px !important; + } + + .sub-title { + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)) !important; + font-size: 14px !important; + font-style: normal; + font-weight: 400 !important; + line-height: 24px !important; + margin-top: 0px !important; + margin-bottom: 24px !important; + } + + .questions-form-container { + max-width: 600px; + width: 600px; + margin: 0 auto; + } + + .questions-form { + width: 100%; + display: flex; + min-height: 420px; + padding: 20px 24px 24px 24px; + flex-direction: column; + align-items: center; + gap: 24px; + border-radius: 4px; + border: 1px solid var(--Greyscale-Slate-500, #161922); + background: var(--Ink-400, #121317); + + .ant-form-item { + margin-bottom: 0px !important; + + .ant-form-item-label { + label { + color: var(--Vanilla-100, #fff) !important; + font-size: 13px !important; + font-weight: 500; + line-height: 20px; + } + } + } + } + + .invite-team-members-container { + display: flex; + width: 100%; + flex-direction: column; + gap: 12px; + + .ant-input-group { + width: 100%; + + .ant-input { + font-size: 12px; + + height: 32px; + background: var(--Ink-300, #16181d); + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + } + + .ant-input-group-addon { + font-size: 11px; + height: 32px; + min-width: 80px; + background: var(--Ink-300, #16181d); + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + border-left: 0px; + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + } + } + } + + .question-label { + color: var(--Vanilla-100, #fff); + font-size: 13px; + font-style: normal; + font-weight: 500; + line-height: 20px; + } + + .question-sub-label { + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + font-size: 11px; + font-style: normal; + font-weight: 400; + line-height: 16px; + } + + .next-prev-container { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + margin-bottom: 24px; + + .ant-btn { + flex: 1; + } + } + + .form-group { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + align-self: stretch; + } + + .slider-container { + width: 100%; + + .ant-slider .ant-slider-mark { + font-size: 10px; + } + } + + .do-later-container { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + margin-top: 24px; + } + + .question { + color: var(--Vanilla-100, #fff); + font-size: 14px; + font-style: normal; + font-weight: 500; + line-height: 20px; + } + + input[type='text'] { + width: 100%; + padding: 12px; + border-radius: 2px; + font-size: 14px; + height: 40px; + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + background: var(--Ink-300, #16181d); + color: #fff; + + &:focus-visible { + outline: none; + } + } + + .radio-group, + .grid, + .tool-grid { + display: flex; + align-items: flex-start; + align-content: flex-start; + gap: 10px; + align-self: stretch; + flex-wrap: wrap; + } + + .radio-button, + .grid-button, + .tool-button { + border-radius: 4px; + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + background: var(--Ink-300, #16181d); + padding: 12px; + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + font-size: 14px; + font-style: normal; + text-align: left; + font-weight: 400; + transition: background-color 0.3s ease; + min-width: 258px; + cursor: pointer; + } + + .radio-button.active, + .grid-button.active, + .tool-button.active, + .radio-button:hover, + .grid-button:hover, + .tool-button:hover { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + + .tool-grid { + grid-template-columns: repeat(4, 1fr); + } + + .input-field { + flex: 0; + padding: 12px; + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + background: var(--Ink-300, #16181d); + color: #fff; + border-radius: 4px; + font-size: 14px; + min-width: 258px; + } + + .next-button { + display: flex; + height: 40px; + padding: 8px 12px 8px 16px; + justify-content: center; + align-items: center; + gap: 6px; + align-self: stretch; + border: 0px; + border-radius: 50px; + margin-top: 24px; + cursor: pointer; + } + + .next-button.disabled { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } + + .arrow { + font-size: 18px; + color: #fff; + } + } + + .onboarding-questionaire-footer { + width: 100%; + height: 60px; + padding: 12px 24px; + box-sizing: border-box; + } +} diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx new file mode 100644 index 0000000000..52f45ca0b5 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -0,0 +1,127 @@ +import { Button, Slider, SliderSingleProps, Typography } from 'antd'; +import { ArrowLeft, ArrowRight, Minus } from 'lucide-react'; + +interface OptimiseSignozNeedsProps { + onNext: () => void; + onBack: () => void; +} + +const logMarks: SliderSingleProps['marks'] = { + 0: '2 GB', + 25: '25 GB', + 50: '50 GB', + 100: '100 GB', +}; + +const hostMarks: SliderSingleProps['marks'] = { + 0: '0', + 20: '20', + 40: '40', + 60: '60', + 80: '80', + 100: '100', +}; + +const serviceMarks: SliderSingleProps['marks'] = { + 0: '0', + 20: '20', + 40: '40', + 60: '60', + 80: '80', + 100: '100', +}; + +function OptimiseSignozNeeds({ + onNext, + onBack, +}: OptimiseSignozNeedsProps): JSX.Element { + return ( +
+ + Optimize SigNoz for Your Needs + + + Give us a quick sense of your scale so SigNoz can keep up! + + +
+
+ + What does your scale approximately look like? + + +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ +
+ + + +
+ +
+ +
+
+
+ ); +} + +export default OptimiseSignozNeeds; diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.styles.scss b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.styles.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx new file mode 100644 index 0000000000..9988c9e97c --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -0,0 +1,257 @@ +/* eslint-disable sonarjs/cognitive-complexity */ +import '../OnboardingQuestionaire.styles.scss'; + +import { Button, Typography } from 'antd'; +import { ArrowRight } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +interface OrgQuestionsProps { + onNext: () => void; +} + +function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { + const [organisationName, setOrganisationName] = useState(''); + const [usesObservability, setUsesObservability] = useState( + null, + ); + const [observabilityTool, setObservabilityTool] = useState( + null, + ); + const [otherTool, setOtherTool] = useState(''); + const [familiarity, setFamiliarity] = useState(null); + const [isNextDisabled, setIsNextDisabled] = useState(true); + + const { user } = useSelector((state) => state.app); + + useEffect(() => { + if ( + organisationName !== '' && + usesObservability !== null && + familiarity !== null && + (observabilityTool !== 'Others' || (usesObservability && otherTool !== '')) + ) { + setIsNextDisabled(false); + } else { + setIsNextDisabled(true); + } + }, [ + organisationName, + usesObservability, + familiarity, + observabilityTool, + otherTool, + ]); + + return ( +
+ + Welcome, {user?.name}! + + + We'll help you get the most out of SigNoz, whether you're new to + observability or a seasoned pro. + + +
+
+
+ + setOrganisationName(e.target.value)} + /> +
+ +
+ + +
+ + +
+
+ + {usesObservability && ( +
+ +
+ + + + + + + + + {observabilityTool === 'Others' ? ( + setOtherTool(e.target.value)} + /> + ) : ( + + )} +
+
+ )} + +
+
+ Are you familiar with observability (o11y)? +
+
+ + + + +
+
+
+ +
+ +
+
+
+ ); +} + +export default OrgQuestions; diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx new file mode 100644 index 0000000000..719da996fc --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -0,0 +1,55 @@ +import './OnboardingQuestionaire.styles.scss'; + +import { useState } from 'react'; + +import { AboutSigNozQuestions } from './AboutSigNozQuestions/AboutSigNozQuestions'; +import InviteTeamMembers from './InviteTeamMembers/InviteTeamMembers'; +import { OnboardingFooter } from './OnboardingFooter/OnboardingFooter'; +import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader'; +import OptimiseSignozNeeds from './OptimiseSignozNeeds/OptimiseSignozNeeds'; +import OrgQuestions from './OrgQuestions/OrgQuestions'; + +function OnboardingQuestionaire(): JSX.Element { + const [currentStep, setCurrentStep] = useState(1); + + return ( +
+
+ +
+ +
+ {currentStep === 1 && ( + setCurrentStep(2)} /> + )} + + {currentStep === 2 && ( + setCurrentStep(1)} + onNext={(): void => setCurrentStep(3)} + /> + )} + + {currentStep === 3 && ( + setCurrentStep(2)} + onNext={(): void => setCurrentStep(4)} + /> + )} + + {currentStep === 4 && ( + setCurrentStep(3)} + onNext={(): void => setCurrentStep(5)} + /> + )} +
+ +
+ +
+
+ ); +} + +export default OnboardingQuestionaire; diff --git a/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx b/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx index ccdc0226f8..7100894a41 100644 --- a/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx +++ b/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx @@ -1,9 +1,9 @@ -import { Typography } from 'antd'; +import OnboardingQuestionaire from 'container/OnboardingQuestionaire'; function OnboardingPageV2(): JSX.Element { return (
- Onboarding V2 +
); } From 6664e1bc022b29bb9b3129fd26515363941ab51e Mon Sep 17 00:00:00 2001 From: Yunus M Date: Fri, 20 Sep 2024 01:51:46 +0530 Subject: [PATCH 06/52] feat: maintain state and add log events --- .../AboutSigNozQuestions.tsx | 238 +++++++++-------- .../InviteTeamMembers/InviteTeamMembers.tsx | 94 +++++-- .../OnboardingQuestionaire.styles.scss | 64 +++++ .../OptimiseSignozNeeds.tsx | 81 +++++- .../OrgQuestions/OrgQuestions.tsx | 243 +++++++++--------- .../OnboardingQuestionaire/index.tsx | 26 +- 6 files changed, 499 insertions(+), 247 deletions(-) diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index 09ca867e5f..c573d552fe 100644 --- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -1,23 +1,52 @@ /* eslint-disable sonarjs/cognitive-complexity */ import '../OnboardingQuestionaire.styles.scss'; -import { Button, Typography } from 'antd'; -import { ArrowLeft, ArrowRight } from 'lucide-react'; +import { Color } from '@signozhq/design-tokens'; +import { Button, Input, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; +import { ArrowLeft, ArrowRight, CheckCircle } from 'lucide-react'; import { useEffect, useState } from 'react'; interface AboutSigNozQuestionsProps { + signozDetails: any; + setSignozDetails: (details: any) => void; onNext: () => void; onBack: () => void; } +const hearAboutSignozOptions: Record = { + blog: 'Blog', + hackerNews: 'Hacker News', + linkedin: 'LinkedIn', + twitter: 'Twitter', + reddit: 'Reddit', + colleaguesFriends: 'Colleagues / Friends', +}; + +const interestedInOptions: Record = { + savingCosts: 'Saving costs', + otelNativeStack: 'Interested in Otel-native stack', + allInOne: 'All in one', +}; + export function AboutSigNozQuestions({ + signozDetails, + setSignozDetails, onNext, onBack, }: AboutSigNozQuestionsProps): JSX.Element { - const [hearAboutSignoz, setHearAboutSignoz] = useState(null); - const [otherAboutSignoz, setOtherAboutSignoz] = useState(''); - const [interestedSignoz, setInterestedSignoz] = useState(null); - const [otherInterest, setOtherInterest] = useState(''); + const [hearAboutSignoz, setHearAboutSignoz] = useState( + signozDetails?.hearAboutSignoz || null, + ); + const [otherAboutSignoz, setOtherAboutSignoz] = useState( + signozDetails?.otherAboutSignoz || '', + ); + const [interestedSignoz, setInterestedSignoz] = useState( + signozDetails?.interestedSignoz || null, + ); + const [otherInterest, setOtherInterest] = useState( + signozDetails?.otherInterest || '', + ); const [isNextDisabled, setIsNextDisabled] = useState(true); useEffect((): void => { @@ -33,6 +62,42 @@ export function AboutSigNozQuestions({ } }, [hearAboutSignoz, otherAboutSignoz, interestedSignoz, otherInterest]); + const handleOnNext = (): void => { + setSignozDetails({ + hearAboutSignoz, + otherAboutSignoz, + interestedSignoz, + otherInterest, + }); + + logEvent('Onboarding: SigNoz Questions: Next', { + hearAboutSignoz, + otherAboutSignoz, + interestedSignoz, + otherInterest, + }); + + onNext(); + }; + + const handleOnBack = (): void => { + setSignozDetails({ + hearAboutSignoz, + otherAboutSignoz, + interestedSignoz, + otherInterest, + }); + + logEvent('Onboarding: SigNoz Questions: Back', { + hearAboutSignoz, + otherAboutSignoz, + interestedSignoz, + otherInterest, + }); + + onBack(); + }; + return (
@@ -46,78 +111,49 @@ export function AboutSigNozQuestions({
Where did you hear about SigNoz?
-
- - - - - - +
+ {Object.keys(hearAboutSignozOptions).map((option: string) => ( + + ))} {hearAboutSignoz === 'Others' ? ( - + ) : ( + '' + ) + } onChange={(e): void => setOtherAboutSignoz(e.target.value)} /> ) : ( - + )}
@@ -126,62 +162,56 @@ export function AboutSigNozQuestions({
What are you interested in doing with SigNoz?
-
- - - +
+ {Object.keys(interestedInOptions).map((option: string) => ( + + ))} {interestedSignoz === 'Others' ? ( - + ) : ( + '' + ) + } onChange={(e): void => setOtherInterest(e.target.value)} /> ) : ( - + )}
- @@ -189,7 +219,7 @@ export function AboutSigNozQuestions({
@@ -63,7 +127,7 @@ function InviteTeamMembers({ Back - diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index df07b47f27..8be4dc608c 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -216,6 +216,7 @@ transition: background-color 0.3s ease; min-width: 258px; cursor: pointer; + box-sizing: border-box; } .radio-button.active, @@ -228,6 +229,61 @@ background: rgba(78, 116, 248, 0.2); } + .two-column-grid { + width: 100%; + display: grid; + grid-template-columns: 1fr 1fr; /* Two equal columns */ + gap: 10px; + } + + .onboarding-questionaire-button, + .add-another-member-button { + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 2px; + border: 1px solid var(--Greyscale-Slate-400, #1d212d); + background: var(--Ink-300, #16181d); + color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + box-shadow: none; + font-size: 14px; + font-style: normal; + text-align: left; + font-weight: 400; + transition: background-color 0.3s ease; + cursor: pointer; + height: 40px; + box-sizing: border-box; + + &:hover { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + + &:focus-visible { + outline: none; + } + + &.active { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + } + + .add-another-member-button { + font-size: 12px; + height: 32px; + } + + .onboarding-questionaire-other-input { + .ant-input-group { + .ant-input { + border-top-right-radius: 0px !important; + border-bottom-right-radius: 0px !important; + } + } + } + .tool-grid { grid-template-columns: repeat(4, 1fr); } @@ -275,4 +331,12 @@ padding: 12px 24px; box-sizing: border-box; } + + .invite-team-members-add-another-member-container { + width: 100%; + display: flex; + justify-content: flex-end; + align-items: center; + margin-top: 12px; + } } diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index 52f45ca0b5..107b1830ef 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -1,7 +1,11 @@ import { Button, Slider, SliderSingleProps, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import { ArrowLeft, ArrowRight, Minus } from 'lucide-react'; +import { useState } from 'react'; interface OptimiseSignozNeedsProps { + optimiseSignozDetails: Record | null; + setOptimiseSignozDetails: (details: Record | null) => void; onNext: () => void; onBack: () => void; } @@ -32,9 +36,69 @@ const serviceMarks: SliderSingleProps['marks'] = { }; function OptimiseSignozNeeds({ + optimiseSignozDetails, + setOptimiseSignozDetails, onNext, onBack, }: OptimiseSignozNeedsProps): JSX.Element { + const [logsPerDay, setLogsPerDay] = useState( + optimiseSignozDetails?.logsPerDay || 25, + ); + const [hostsPerDay, setHostsPerDay] = useState( + optimiseSignozDetails?.hostsPerDay || 40, + ); + const [services, setServices] = useState( + optimiseSignozDetails?.services || 10, + ); + + const handleOnNext = (): void => { + setOptimiseSignozDetails({ + logsPerDay, + hostsPerDay, + services, + }); + + logEvent('Onboarding: Optimise SigNoz Needs: Next', { + logsPerDay, + hostsPerDay, + services, + }); + + onNext(); + }; + + const handleOnBack = (): void => { + setOptimiseSignozDetails({ + logsPerDay, + hostsPerDay, + services, + }); + + logEvent('Onboarding: Optimise SigNoz Needs: Back', { + logsPerDay, + hostsPerDay, + services, + }); + + onBack(); + }; + + const handleWillDoLater = (): void => { + setOptimiseSignozDetails({ + logsPerDay: 0, + hostsPerDay: 0, + services: 0, + }); + + logEvent('Onboarding: Optimise SigNoz Needs: Will do later', { + logsPerDay: 0, + hostsPerDay: 0, + services: 0, + }); + + onNext(); + }; + return (
@@ -57,7 +121,8 @@ function OptimiseSignozNeeds({
setLogsPerDay(value)} styles={{ track: { background: '#4E74F8', @@ -69,12 +134,13 @@ function OptimiseSignozNeeds({
setHostsPerDay(value)} styles={{ track: { background: '#4E74F8', @@ -91,7 +157,8 @@ function OptimiseSignozNeeds({
setServices(value)} styles={{ track: { background: '#4E74F8', @@ -103,19 +170,19 @@ function OptimiseSignozNeeds({
- -
-
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index 9988c9e97c..6cd679c5da 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -1,27 +1,58 @@ /* eslint-disable sonarjs/cognitive-complexity */ import '../OnboardingQuestionaire.styles.scss'; -import { Button, Typography } from 'antd'; -import { ArrowRight } from 'lucide-react'; +import { Color } from '@signozhq/design-tokens'; +import { Button, Input, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; +import { ArrowRight, CheckCircle } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; interface OrgQuestionsProps { + orgDetails: any; + setOrgDetails: (details: any) => void; onNext: () => void; } -function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { - const [organisationName, setOrganisationName] = useState(''); +const observabilityTools = [ + 'AWS Cloudwatch', + 'DataDog', + 'New Relic', + 'Grafana / Prometheus', + 'Azure App Monitor', + 'GCP-native o11y tools', + 'Honeycomb', +]; + +const o11yFamiliarityOptions: Record = { + new: "I'm completely new", + builtStack: "I've built a stack before", + experienced: 'I have some experience', + dontKnow: "I don't know what it is", +}; + +function OrgQuestions({ + orgDetails, + setOrgDetails, + onNext, +}: OrgQuestionsProps): JSX.Element { + const [organisationName, setOrganisationName] = useState( + orgDetails?.organisationName || '', + ); const [usesObservability, setUsesObservability] = useState( - null, + orgDetails?.usesObservability || null, ); const [observabilityTool, setObservabilityTool] = useState( - null, + orgDetails?.observabilityTool || null, + ); + const [otherTool, setOtherTool] = useState( + orgDetails?.otherTool || '', + ); + const [familiarity, setFamiliarity] = useState( + orgDetails?.familiarity || null, ); - const [otherTool, setOtherTool] = useState(''); - const [familiarity, setFamiliarity] = useState(null); const [isNextDisabled, setIsNextDisabled] = useState(true); const { user } = useSelector((state) => state.app); @@ -45,6 +76,26 @@ function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { otherTool, ]); + const handleOnNext = (): void => { + setOrgDetails({ + organisationName, + usesObservability, + observabilityTool, + otherTool, + familiarity, + }); + + logEvent('Onboarding: Org Questions: Next', { + organisationName, + usesObservability, + observabilityTool, + otherTool, + familiarity, + }); + + onNext(); + }; + return (
@@ -77,20 +128,25 @@ function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { Do you currently use any observability/monitoring tool? -
- - + + No{' '} + {usesObservability === false && ( + + )} +
@@ -109,83 +168,44 @@ function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { -
- - - - - - - +
+ {observabilityTools.map((tool) => ( + + ))} {observabilityTool === 'Others' ? ( - + ) : ( + '' + ) + } onChange={(e): void => setOtherTool(e.target.value)} /> ) : ( - - - +
+ {Object.keys(o11yFamiliarityOptions).map((option: string) => ( + + ))}
@@ -242,7 +245,7 @@ function OrgQuestions({ onNext }: OrgQuestionsProps): JSX.Element { ))} - {interestedSignoz === 'Others' ? ( + {interestInSignoz === 'Others' ? ( ) : ( '' ) } - onChange={(e): void => setOtherInterest(e.target.value)} + onChange={(e): void => setOtherInterestInSignoz(e.target.value)} /> ) : ( diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss new file mode 100644 index 0000000000..aae10276f6 --- /dev/null +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -0,0 +1,46 @@ +.team-member-container { + display: flex; + align-items: center; + + .team-member-role-select { + width: 20%; + + .ant-select-selector { + border: 1px solid #1d212d; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + + border-right: none; + } + } + + .team-member-email-input { + width: 80%; + + border: 1px solid #1d212d; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + } +} + +.questions-form-container { + .error-message-container { + padding: 16px; + margin-top: 16px; + border-radius: 4px; + border: 1px solid var(--bg-slate-500, #161922); + background: var(--bg-ink-400, #121317); + width: 100%; + display: flex; + align-items: center; + + .error-message { + font-size: 12px; + font-weight: 400; + + display: flex; + align-items: center; + gap: 8px; + } + } +} diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index 6eeb2c5e6f..cd5f0d53c4 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -1,5 +1,8 @@ +import './InviteTeamMembers.styles.scss'; + import { Color } from '@signozhq/design-tokens'; import { Button, Input, Select, Typography } from 'antd'; +import { cloneDeep, debounce, isEmpty } from 'lodash-es'; import { ArrowLeft, ArrowRight, @@ -7,55 +10,123 @@ import { Plus, TriangleAlert, } from 'lucide-react'; -import { useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { v4 as uuid } from 'uuid'; + +interface TeamMember { + email: string; + role: string; + name: string; + frontendBaseUrl: string; + id: string; +} interface InviteTeamMembersProps { - teamMembers: string[]; - setTeamMembers: (teamMembers: string[]) => void; + teamMembers: TeamMember[] | null; + setTeamMembers: (teamMembers: TeamMember[]) => void; onNext: () => void; onBack: () => void; } -const userRolesOptions = ( - -); - function InviteTeamMembers({ teamMembers, setTeamMembers, onNext, onBack, }: InviteTeamMembersProps): JSX.Element { - const [teamMembersToInvite, setTeamMembersToInvite] = useState( - teamMembers || [''], + const [teamMembersToInvite, setTeamMembersToInvite] = useState< + TeamMember[] | null + >(teamMembers); + + const [emailValidity, setEmailValidity] = useState>( + {}, ); + const [hasInvalidEmails, setHasInvalidEmails] = useState(false); + + const defaultTeamMember: TeamMember = { + email: '', + role: 'EDITOR', + name: '', + frontendBaseUrl: '', + id: '', + }; + + useEffect(() => { + if (isEmpty(teamMembers)) { + const teamMember = { + ...defaultTeamMember, + id: uuid(), + }; + + setTeamMembersToInvite([teamMember]); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [teamMembers]); + const handleAddTeamMember = (): void => { - setTeamMembersToInvite([...teamMembersToInvite, '']); + const newTeamMember = { ...defaultTeamMember, id: uuid() }; + setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]); + }; + + // Validation function to check all users + const validateAllUsers = (): boolean => { + let isValid = true; + + const updatedValidity: Record = {}; + + teamMembersToInvite?.forEach((member) => { + const emailValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(member.email); + if (!emailValid || !member.email) { + isValid = false; + setHasInvalidEmails(true); + } + updatedValidity[member.id!] = emailValid; + }); + + setEmailValidity(updatedValidity); + + return isValid; }; const handleNext = (): void => { - console.log(teamMembersToInvite); - setTeamMembers(teamMembersToInvite); - onNext(); + if (validateAllUsers()) { + setTeamMembers(teamMembersToInvite || []); + onNext(); + } }; - const handleOnChange = ( + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedValidateEmail = useCallback( + debounce((email: string, memberId: string) => { + const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); + setEmailValidity((prev) => ({ ...prev, [memberId]: isValid })); + }, 500), + [], + ); + + const handleEmailChange = ( e: React.ChangeEvent, - index: number, + member: TeamMember, ): void => { - const newTeamMembers = [...teamMembersToInvite]; - newTeamMembers[index] = e.target.value; - setTeamMembersToInvite(newTeamMembers); + const { value } = e.target; + const updatedMembers = cloneDeep(teamMembersToInvite || []); + + const memberToUpdate = updatedMembers.find((m) => m.id === member.id); + if (memberToUpdate) { + memberToUpdate.email = value; + setTeamMembersToInvite(updatedMembers); + debouncedValidateEmail(value, member.id!); + } }; - const isValidEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); + const handleRoleChange = (role: string, member: TeamMember): void => { + const updatedMembers = cloneDeep(teamMembersToInvite || []); + const memberToUpdate = updatedMembers.find((m) => m.id === member.id); + if (memberToUpdate) { + memberToUpdate.role = role; + setTeamMembersToInvite(updatedMembers); + } }; return ( @@ -79,29 +150,37 @@ function InviteTeamMembers({
- {teamMembersToInvite.map((member, index) => ( - // eslint-disable-next-line react/no-array-index-key -
+ {teamMembersToInvite?.map((member) => ( +
+ 0 ? ( - isValidEmail(member) ? ( - - ) : ( - - ) - ) : null - } placeholder="your-teammate@org.com" - value={member} + value={member.email} type="email" required autoFocus autoComplete="off" + className="team-member-email-input" onChange={(e: React.ChangeEvent): void => - handleOnChange(e, index) + handleEmailChange(e, member) + } + addonAfter={ + // eslint-disable-next-line no-nested-ternary + emailValidity[member.id!] === undefined ? null : emailValidity[ + member.id! + ] ? ( + + ) : ( + + ) } />
@@ -121,6 +200,15 @@ function InviteTeamMembers({
+ {hasInvalidEmails && ( +
+ + Please enter valid emails for all team + members + +
+ )} +
- -
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index 6cd679c5da..a1fea0aaf8 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -4,27 +4,42 @@ import '../OnboardingQuestionaire.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; -import { ArrowRight, CheckCircle } from 'lucide-react'; +import { ArrowRight, CheckCircle, Loader2 } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; +export interface OrgData { + id: string; + isAnonymous: boolean; + name: string; +} + +export interface OrgDetails { + organisationName: string; + usesObservability: boolean | null; + observabilityTool: string | null; + otherTool: string | null; + familiarity: string | null; +} + interface OrgQuestionsProps { - orgDetails: any; - setOrgDetails: (details: any) => void; + isLoading: boolean; + orgDetails: OrgDetails; + setOrgDetails: (details: OrgDetails) => void; onNext: () => void; } -const observabilityTools = [ - 'AWS Cloudwatch', - 'DataDog', - 'New Relic', - 'Grafana / Prometheus', - 'Azure App Monitor', - 'GCP-native o11y tools', - 'Honeycomb', -]; +const observabilityTools = { + AWSCloudwatch: 'AWS Cloudwatch', + DataDog: 'DataDog', + NewRelic: 'New Relic', + GrafanaPrometheus: 'Grafana / Prometheus', + AzureAppMonitor: 'Azure App Monitor', + GCPNativeO11yTools: 'GCP-native o11y tools', + Honeycomb: 'Honeycomb', +}; const o11yFamiliarityOptions: Record = { new: "I'm completely new", @@ -34,10 +49,13 @@ const o11yFamiliarityOptions: Record = { }; function OrgQuestions({ + isLoading, orgDetails, setOrgDetails, onNext, }: OrgQuestionsProps): JSX.Element { + const { user } = useSelector((state) => state.app); + const [organisationName, setOrganisationName] = useState( orgDetails?.organisationName || '', ); @@ -55,19 +73,36 @@ function OrgQuestions({ ); const [isNextDisabled, setIsNextDisabled] = useState(true); - const { user } = useSelector((state) => state.app); + useEffect(() => { + setOrganisationName(orgDetails.organisationName); + }, [orgDetails.organisationName]); + + const isValidUsesObservability = (): boolean => { + if (usesObservability === null) { + return false; + } + + if (usesObservability && (!observabilityTool || observabilityTool === '')) { + return false; + } + + // eslint-disable-next-line sonarjs/prefer-single-boolean-return + if (usesObservability && observabilityTool === 'Others' && otherTool === '') { + return false; + } + + return true; + }; useEffect(() => { - if ( - organisationName !== '' && - usesObservability !== null && - familiarity !== null && - (observabilityTool !== 'Others' || (usesObservability && otherTool !== '')) - ) { + const isValidObservability = isValidUsesObservability(); + + if (organisationName !== '' && familiarity !== null && isValidObservability) { setIsNextDisabled(false); } else { setIsNextDisabled(true); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ organisationName, usesObservability, @@ -115,7 +150,7 @@ function OrgQuestions({
- {observabilityTools.map((tool) => ( + {Object.keys(observabilityTools).map((tool) => (
diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index bb9e88d0d4..39e34e1f82 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -1,30 +1,194 @@ import './OnboardingQuestionaire.styles.scss'; -import { useState } from 'react'; +import { NotificationInstance } from 'antd/es/notification/interface'; +import updateProfileAPI from 'api/onboarding/updateProfile'; +import editOrg from 'api/user/editOrg'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; +import { useNotifications } from 'hooks/useNotifications'; +import history from 'lib/history'; +import { Dispatch, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useMutation } from 'react-query'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_ORG_NAME } from 'types/actions/app'; +import AppReducer from 'types/reducer/app'; -import { AboutSigNozQuestions } from './AboutSigNozQuestions/AboutSigNozQuestions'; +import { + AboutSigNozQuestions, + SignozDetails, +} from './AboutSigNozQuestions/AboutSigNozQuestions'; import InviteTeamMembers from './InviteTeamMembers/InviteTeamMembers'; import { OnboardingFooter } from './OnboardingFooter/OnboardingFooter'; import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader'; -import OptimiseSignozNeeds from './OptimiseSignozNeeds/OptimiseSignozNeeds'; -import OrgQuestions from './OrgQuestions/OrgQuestions'; +import OptimiseSignozNeeds, { + OptimiseSignozDetails, +} from './OptimiseSignozNeeds/OptimiseSignozNeeds'; +import OrgQuestions, { OrgData, OrgDetails } from './OrgQuestions/OrgQuestions'; + +export const showErrorNotification = ( + notifications: NotificationInstance, + err: Error, +): void => { + notifications.error({ + message: err.message || SOMETHING_WENT_WRONG, + }); +}; + +const INITIAL_ORG_DETAILS: OrgDetails = { + organisationName: '', + usesObservability: true, + observabilityTool: '', + otherTool: '', + familiarity: '', +}; + +const INITIAL_SIGNOZ_DETAILS: SignozDetails = { + hearAboutSignoz: '', + interestInSignoz: '', + otherInterestInSignoz: '', + otherAboutSignoz: '', +}; + +const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { + logsPerDay: 25, + hostsPerDay: 40, + services: 10, +}; function OnboardingQuestionaire(): JSX.Element { + const { notifications } = useNotifications(); + const [currentStep, setCurrentStep] = useState(1); - - const [orgDetails, setOrgDetails] = useState | null>( - null, + const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); + const [signozDetails, setSignozDetails] = useState( + INITIAL_SIGNOZ_DETAILS, ); - const [signozDetails, setSignozDetails] = useState | null>(null); - const [optimiseSignozDetails, setOptimiseSignozDetails] = useState | null>(null); + const [ + optimiseSignozDetails, + setOptimiseSignozDetails, + ] = useState(INITIAL_OPTIMISE_SIGNOZ_DETAILS); + const [teamMembers, setTeamMembers] = useState< + InviteTeamMembersProps[] | null + >(null); - const [teamMembers, setTeamMembers] = useState(['']); + const { t } = useTranslation(['organizationsettings', 'common']); + const { org } = useSelector((state) => state.app); + const dispatch = useDispatch>(); + const [orgData, setOrgData] = useState(null); + + useEffect(() => { + if (org) { + setOrgData(org[0]); + + setOrgDetails({ + ...orgDetails, + organisationName: org[0].name, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [org]); + + const [isLoading, setIsLoading] = useState(false); + + const handleOrgNameUpdate = async (): Promise => { + /* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */ + if ( + !orgData || + !orgDetails.organisationName || + orgDetails.organisationName === '' || + orgData.name === orgDetails.organisationName + ) { + setCurrentStep(2); + + return; + } + + try { + setIsLoading(true); + const { statusCode, error } = await editOrg({ + isAnonymous: orgData?.isAnonymous, + name: orgDetails.organisationName, + orgId: orgData?.id, + }); + if (statusCode === 200) { + dispatch({ + type: UPDATE_ORG_NAME, + payload: { + orgId: orgData?.id, + name: orgDetails.organisationName, + }, + }); + + setCurrentStep(2); + } else { + notifications.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + notifications.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + const handleOrgDetailsUpdate = (): void => { + handleOrgNameUpdate(); + }; + + const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation( + updateProfileAPI, + { + onSuccess: (data) => { + console.log('data', data); + + setCurrentStep(4); + }, + onError: (error) => { + showErrorNotification(notifications, error as AxiosError); + }, + }, + ); + + const handleUpdateProfile = (): void => { + updateProfile({ + familiarity_with_observability: orgDetails?.familiarity as string, + has_existing_observability_tool: orgDetails?.usesObservability as boolean, + existing_observability_tool: + orgDetails?.observabilityTool === 'Others' + ? (orgDetails?.otherTool as string) + : (orgDetails?.observabilityTool as string), + + reasons_for_interest_in_signoz: + signozDetails?.interestInSignoz === 'Others' + ? (signozDetails?.otherInterestInSignoz as string) + : (signozDetails?.interestInSignoz as string), + where_did_you_hear_about_signoz: + signozDetails?.hearAboutSignoz === 'Others' + ? (signozDetails?.otherAboutSignoz as string) + : (signozDetails?.hearAboutSignoz as string), + + logs_scale_per_day_in_gb: optimiseSignozDetails?.logsPerDay as number, + number_of_hosts: optimiseSignozDetails?.hostsPerDay as number, + number_of_services: optimiseSignozDetails?.services as number, + }); + }; + + const handleOnboardingComplete = (): void => { + history.push('/'); + }; return (
@@ -35,9 +199,10 @@ function OnboardingQuestionaire(): JSX.Element {
{currentStep === 1 && ( setCurrentStep(2)} + onNext={handleOrgDetailsUpdate} /> )} @@ -52,10 +217,11 @@ function OnboardingQuestionaire(): JSX.Element { {currentStep === 3 && ( setCurrentStep(2)} - onNext={(): void => setCurrentStep(4)} + onNext={handleUpdateProfile} /> )} @@ -64,7 +230,7 @@ function OnboardingQuestionaire(): JSX.Element { teamMembers={teamMembers} setTeamMembers={setTeamMembers} onBack={(): void => setCurrentStep(3)} - onNext={(): void => setCurrentStep(5)} + onNext={handleOnboardingComplete} /> )}
diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx index 7c4909ab8d..07c1d8f219 100644 --- a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx @@ -236,7 +236,9 @@ function PendingInvitesContainer(): JSX.Element { export interface InviteTeamMembersProps { email: string; name: string; - role: ROLES; + role: string; + id: string; + frontendBaseUrl: string; } interface DataProps { diff --git a/frontend/src/types/api/onboarding/types.ts b/frontend/src/types/api/onboarding/types.ts new file mode 100644 index 0000000000..046b7df18d --- /dev/null +++ b/frontend/src/types/api/onboarding/types.ts @@ -0,0 +1,10 @@ +export interface UpdateProfileProps { + reasons_for_interest_in_signoz: string; + familiarity_with_observability: string; + has_existing_observability_tool: boolean; + existing_observability_tool: string; + logs_scale_per_day_in_gb: number; + number_of_services: number; + number_of_hosts: number; + where_did_you_hear_about_signoz: string; +} diff --git a/frontend/src/types/api/user/inviteUsers.ts b/frontend/src/types/api/user/inviteUsers.ts new file mode 100644 index 0000000000..cf12269e73 --- /dev/null +++ b/frontend/src/types/api/user/inviteUsers.ts @@ -0,0 +1,17 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +export interface UserProps { + name: User['name']; + email: User['email']; + role: ROLES; + frontendBaseUrl: string; +} + +export interface UsersProps { + users: UserProps[]; +} + +export interface PayloadProps { + data: string; +} From 4dc5615d2ff6306ae8deddbb25e9c01893a2456c Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 23 Oct 2024 20:08:23 +0530 Subject: [PATCH 08/52] feat: handle errors for profiles and invite users api --- frontend/public/locales/en/titles.json | 1 + frontend/src/api/onboarding/updateProfile.ts | 24 +++----- frontend/src/api/user/inviteUsers.ts | 23 +++---- .../InviteTeamMembers.styles.scss | 1 + .../InviteTeamMembers/InviteTeamMembers.tsx | 61 ++++++++++++++++--- .../OptimiseSignozNeeds.tsx | 4 ++ .../OnboardingQuestionaire/index.tsx | 4 +- frontend/src/types/api/user/inviteUsers.ts | 3 +- frontend/src/utils/error.ts | 12 ++++ 9 files changed, 93 insertions(+), 40 deletions(-) create mode 100644 frontend/src/utils/error.ts diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index 126b8a7ac1..aa59005c3f 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -4,6 +4,7 @@ "SERVICE_METRICS": "SigNoz | Service Metrics", "SERVICE_MAP": "SigNoz | Service Map", "GET_STARTED": "SigNoz | Get Started", + "GET_STARTED_V2": "SigNoz | Get Started", "GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM", "GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs", "GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure", diff --git a/frontend/src/api/onboarding/updateProfile.ts b/frontend/src/api/onboarding/updateProfile.ts index f7f5ffe22c..a9e2d44653 100644 --- a/frontend/src/api/onboarding/updateProfile.ts +++ b/frontend/src/api/onboarding/updateProfile.ts @@ -1,26 +1,20 @@ import { GatewayApiV2Instance } from 'api'; -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { UpdateProfileProps } from 'types/api/onboarding/types'; const updateProfile = async ( props: UpdateProfileProps, ): Promise | ErrorResponse> => { - try { - const response = await GatewayApiV2Instance.put('/profiles/me', { - ...props, - }); + const response = await GatewayApiV2Instance.put('/profiles/me', { + ...props, + }); - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; }; export default updateProfile; diff --git a/frontend/src/api/user/inviteUsers.ts b/frontend/src/api/user/inviteUsers.ts index 6722b26593..28189159ff 100644 --- a/frontend/src/api/user/inviteUsers.ts +++ b/frontend/src/api/user/inviteUsers.ts @@ -1,25 +1,18 @@ -import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; -import axios, { AxiosError } from 'axios'; +import axios from 'api'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers'; const inviteUsers = async ( users: UsersProps, ): Promise | ErrorResponse> => { - try { - const response = await axios.post(`/invite/bulk`, { - ...users, - }); + const response = await axios.post(`/invite/bulk`, users); - return { - statusCode: 200, - error: null, - message: response.data.status, - payload: response.data, - }; - } catch (error) { - return ErrorResponseHandler(error as AxiosError); - } + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; }; export default inviteUsers; diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss index aae10276f6..263e1d4e80 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -41,6 +41,7 @@ display: flex; align-items: center; gap: 8px; + text-transform: capitalize; } } } diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index cd5f0d53c4..e4d2a64bc7 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -2,6 +2,8 @@ import './InviteTeamMembers.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Select, Typography } from 'antd'; +import inviteUsers from 'api/user/inviteUsers'; +import { AxiosError } from 'axios'; import { cloneDeep, debounce, isEmpty } from 'lodash-es'; import { ArrowLeft, @@ -11,6 +13,8 @@ import { TriangleAlert, } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; +import { useMutation } from 'react-query'; +import { ErrorResponse } from 'types/api'; import { v4 as uuid } from 'uuid'; interface TeamMember { @@ -37,18 +41,17 @@ function InviteTeamMembers({ const [teamMembersToInvite, setTeamMembersToInvite] = useState< TeamMember[] | null >(teamMembers); - const [emailValidity, setEmailValidity] = useState>( {}, ); - const [hasInvalidEmails, setHasInvalidEmails] = useState(false); + const [error, setError] = useState(null); const defaultTeamMember: TeamMember = { email: '', role: 'EDITOR', name: '', - frontendBaseUrl: '', + frontendBaseUrl: window.location.origin, id: '', }; @@ -65,7 +68,10 @@ function InviteTeamMembers({ }, [teamMembers]); const handleAddTeamMember = (): void => { - const newTeamMember = { ...defaultTeamMember, id: uuid() }; + const newTeamMember = { + ...defaultTeamMember, + id: uuid(), + }; setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]); }; @@ -89,10 +95,34 @@ function InviteTeamMembers({ return isValid; }; + const handleError = (error: AxiosError): void => { + const errorMessage = error.response?.data as ErrorResponse; + + setError(errorMessage.error); + }; + + const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation( + inviteUsers, + { + onSuccess: (): void => { + onNext(); + }, + onError: (error): void => { + handleError(error as AxiosError); + }, + }, + ); + const handleNext = (): void => { if (validateAllUsers()) { setTeamMembers(teamMembersToInvite || []); - onNext(); + + setError(null); + setHasInvalidEmails(false); + + sendInvites({ + users: teamMembersToInvite || [], + }); } }; @@ -129,6 +159,10 @@ function InviteTeamMembers({ } }; + const handleDoLater = (): void => { + onNext(); + }; + return (
@@ -209,20 +243,33 @@ function InviteTeamMembers({
)} + {error && ( +
+ + {error} + +
+ )} +
-
-
diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index f5d4d410c1..d564158059 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -14,6 +14,7 @@ interface OptimiseSignozNeedsProps { setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void; onNext: () => void; onBack: () => void; + onWillDoLater: () => void; isUpdatingProfile: boolean; } @@ -48,6 +49,7 @@ function OptimiseSignozNeeds({ setOptimiseSignozDetails, onNext, onBack, + onWillDoLater, }: OptimiseSignozNeedsProps): JSX.Element { const [logsPerDay, setLogsPerDay] = useState( optimiseSignozDetails?.logsPerDay || 25, @@ -95,6 +97,8 @@ function OptimiseSignozNeeds({ services: 0, }); + onWillDoLater(); + logEvent('Onboarding: Optimise SigNoz Needs: Will do later', { logsPerDay: 0, hostsPerDay: 0, diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 39e34e1f82..974070b944 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -5,6 +5,7 @@ import updateProfileAPI from 'api/onboarding/updateProfile'; import editOrg from 'api/user/editOrg'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; +import ROUTES from 'constants/routes'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; @@ -187,7 +188,7 @@ function OnboardingQuestionaire(): JSX.Element { }; const handleOnboardingComplete = (): void => { - history.push('/'); + history.push(ROUTES.APPLICATION); }; return ( @@ -222,6 +223,7 @@ function OnboardingQuestionaire(): JSX.Element { setOptimiseSignozDetails={setOptimiseSignozDetails} onBack={(): void => 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 /> )} diff --git a/frontend/src/types/api/user/inviteUsers.ts b/frontend/src/types/api/user/inviteUsers.ts index cf12269e73..8491e31cc4 100644 --- a/frontend/src/types/api/user/inviteUsers.ts +++ b/frontend/src/types/api/user/inviteUsers.ts @@ -1,10 +1,9 @@ import { User } from 'types/reducer/app'; -import { ROLES } from 'types/roles'; export interface UserProps { name: User['name']; email: User['email']; - role: ROLES; + role: string; frontendBaseUrl: string; } diff --git a/frontend/src/utils/error.ts b/frontend/src/utils/error.ts new file mode 100644 index 0000000000..7a0318a3ab --- /dev/null +++ b/frontend/src/utils/error.ts @@ -0,0 +1,12 @@ +import { NotificationInstance } from 'antd/es/notification/interface'; +import axios from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; + +export const showErrorNotification = ( + notifications: NotificationInstance, + err: Error, +): void => { + notifications.error({ + message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG, + }); +}; From abc2ec2155d36658d2a75082bf5c479c984c8e1e Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 23 Oct 2024 23:06:28 +0530 Subject: [PATCH 09/52] feat: handle redirection after onboarding --- frontend/public/locales/en/titles.json | 2 +- frontend/src/AppRoutes/routes.ts | 4 +- frontend/src/constants/routes.ts | 2 +- frontend/src/container/AppLayout/index.tsx | 2 +- .../InviteTeamMembers.styles.scss | 19 ++++--- .../InviteTeamMembers/InviteTeamMembers.tsx | 34 +++++++++---- .../OnboardingQuestionaire.styles.scss | 50 ++++++++++++++++++- .../OnboardingQuestionaire/index.tsx | 16 +++++- frontend/src/container/SideNav/config.ts | 2 +- frontend/src/container/SideNav/menuItems.tsx | 11 ---- .../container/TopNav/Breadcrumbs/index.tsx | 1 - frontend/src/utils/permission/index.ts | 2 +- 12 files changed, 105 insertions(+), 40 deletions(-) diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index aa59005c3f..4d3e899d76 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -4,7 +4,7 @@ "SERVICE_METRICS": "SigNoz | Service Metrics", "SERVICE_MAP": "SigNoz | Service Map", "GET_STARTED": "SigNoz | Get Started", - "GET_STARTED_V2": "SigNoz | Get Started", + "ONBOARDING": "SigNoz | Get Started", "GET_STARTED_APPLICATION_MONITORING": "SigNoz | Get Started | APM", "GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs", "GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure", diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index fc59fef1e3..1901095a67 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -70,11 +70,11 @@ const routes: AppRoutes[] = [ key: 'GET_STARTED', }, { - path: ROUTES.GET_STARTED_V2, + path: ROUTES.ONBOARDING, exact: false, component: OnboardingV2, isPrivate: true, - key: 'GET_STARTED_V2', + key: 'ONBOARDING', }, { component: LogsIndexToFields, diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 50d189f4a8..3c17336889 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -8,7 +8,7 @@ const ROUTES = { TRACE_DETAIL: '/trace/:id', TRACES_EXPLORER: '/traces-explorer', GET_STARTED: '/get-started', - GET_STARTED_V2: '/get-started-v2', + ONBOARDING: '/onboarding', GET_STARTED_APPLICATION_MONITORING: '/get-started/application-monitoring', GET_STARTED_LOGS_MANAGEMENT: '/get-started/logs-management', GET_STARTED_INFRASTRUCTURE_MONITORING: diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 898481e2a5..700914cc6a 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -191,7 +191,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const pageTitle = t(routeKey); const renderFullScreen = pathname === ROUTES.GET_STARTED || - pathname === ROUTES.GET_STARTED_V2 || + pathname === ROUTES.ONBOARDING || pathname === ROUTES.WORKSPACE_LOCKED || pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING || pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING || diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss index 263e1d4e80..ae26562b3a 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -7,19 +7,24 @@ .ant-select-selector { border: 1px solid #1d212d; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; - - border-right: none; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; } } .team-member-email-input { width: 80%; + background-color: #121317; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; - border: 1px solid #1d212d; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; + .ant-input, + .ant-input-group-addon { + background-color: #121317 !important; + border-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + } } } diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index e4d2a64bc7..4adfd92c6e 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -11,6 +11,7 @@ import { CheckCircle, Plus, TriangleAlert, + X, } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { useMutation } from 'react-query'; @@ -75,6 +76,10 @@ function InviteTeamMembers({ setTeamMembersToInvite((prev) => [...(prev || []), newTeamMember]); }; + const handleRemoveTeamMember = (id: string): void => { + setTeamMembersToInvite((prev) => (prev || []).filter((m) => m.id !== id)); + }; + // Validation function to check all users const validateAllUsers = (): boolean => { let isValid = true; @@ -174,7 +179,7 @@ function InviteTeamMembers({
-
+
Collaborate with your team @@ -186,15 +191,6 @@ function InviteTeamMembers({
{teamMembersToInvite?.map((member) => (
- + + + {teamMembersToInvite?.length > 1 && ( +
))}
diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index 8be4dc608c..00b091d1d8 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -83,6 +83,31 @@ } } } + + &.invite-team-members-form { + min-height: calc(420px - 24px); + max-height: calc(420px - 24px); + + .invite-team-members-container { + max-height: 260px; + padding-right: 8px; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.1rem; + } + &::-webkit-scrollbar-corner { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: rgb(136, 136, 136); + border-radius: 0.625rem; + } + &::-webkit-scrollbar-track { + background: transparent; + } + } + } } .invite-team-members-container { @@ -173,6 +198,10 @@ font-style: normal; font-weight: 500; line-height: 20px; + + display: flex; + align-items: center; + gap: 8px; } input[type='text'] { @@ -237,7 +266,8 @@ } .onboarding-questionaire-button, - .add-another-member-button { + .add-another-member-button, + .remove-team-member-button { display: flex; align-items: center; justify-content: space-between; @@ -270,11 +300,27 @@ } } - .add-another-member-button { + .add-another-member-button, + .remove-team-member-button { font-size: 12px; height: 32px; } + .remove-team-member-button { + display: flex; + align-items: center; + justify-content: center; + + border: 1px solid #1d212d; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + background-color: #121317; + + border-left: 0px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + } + .onboarding-questionaire-other-input { .ant-input-group { .ant-input { diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 974070b944..a5d1f83254 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -3,6 +3,7 @@ import './OnboardingQuestionaire.styles.scss'; import { NotificationInstance } from 'antd/es/notification/interface'; import updateProfileAPI from 'api/onboarding/updateProfile'; import editOrg from 'api/user/editOrg'; +import getOrgUser from 'api/user/getOrgUser'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import ROUTES from 'constants/routes'; @@ -11,7 +12,7 @@ import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { Dispatch, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useMutation } from 'react-query'; +import { useMutation, useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; @@ -78,6 +79,17 @@ function OnboardingQuestionaire(): JSX.Element { const { t } = useTranslation(['organizationsettings', 'common']); const { org } = useSelector((state) => state.app); + + const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({ + queryFn: () => + getOrgUser({ + orgId: (org || [])[0].id, + }), + queryKey: ['getOrgUser', org?.[0].id], + }); + + console.log('orgUsers', orgUsers, isLoadingOrgUsers); + const dispatch = useDispatch>(); const [orgData, setOrgData] = useState(null); @@ -188,7 +200,7 @@ function OnboardingQuestionaire(): JSX.Element { }; const handleOnboardingComplete = (): void => { - history.push(ROUTES.APPLICATION); + history.push(ROUTES.GET_STARTED); }; return ( diff --git a/frontend/src/container/SideNav/config.ts b/frontend/src/container/SideNav/config.ts index 7b37f64bb7..7fdd462a52 100644 --- a/frontend/src/container/SideNav/config.ts +++ b/frontend/src/container/SideNav/config.ts @@ -27,7 +27,7 @@ export const routeConfig: Record = { [ROUTES.ERROR_DETAIL]: [QueryParams.resourceAttributes], [ROUTES.HOME_PAGE]: [QueryParams.resourceAttributes], [ROUTES.GET_STARTED]: [QueryParams.resourceAttributes], - [ROUTES.GET_STARTED_V2]: [QueryParams.resourceAttributes], + [ROUTES.ONBOARDING]: [QueryParams.resourceAttributes], [ROUTES.LIST_ALL_ALERT]: [QueryParams.resourceAttributes], [ROUTES.LIST_LICENSES]: [QueryParams.resourceAttributes], [ROUTES.LOGIN]: [QueryParams.resourceAttributes], diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index a1c41ddea8..6d24b74c53 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -29,12 +29,6 @@ export const getStartedMenuItem = { icon: , }; -export const getStartedV2MenuItem = { - key: ROUTES.GET_STARTED_V2, - label: 'Get Started V2', - icon: , -}; - export const inviteMemberMenuItem = { key: `${ROUTES.ORG_SETTINGS}#invite-team-members`, label: 'Invite Team Member', @@ -72,11 +66,6 @@ export const trySignozCloudMenuItem: SidebarItem = { }; const menuItems: SidebarItem[] = [ - { - key: ROUTES.GET_STARTED_V2, - label: 'Get Started V2', - icon: , - }, { key: ROUTES.APPLICATION, label: 'Services', diff --git a/frontend/src/container/TopNav/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx index c98f4d05e2..9efd50d2c3 100644 --- a/frontend/src/container/TopNav/Breadcrumbs/index.tsx +++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx @@ -9,7 +9,6 @@ const breadcrumbNameMap: Record = { [ROUTES.SERVICE_MAP]: 'Service Map', [ROUTES.USAGE_EXPLORER]: 'Usage Explorer', [ROUTES.GET_STARTED]: 'Get Started', - [ROUTES.GET_STARTED_V2]: 'Get Started V2', [ROUTES.ALL_CHANNELS]: 'Channels', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index 6052900a05..e58843232a 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -86,7 +86,7 @@ export const routePermission: Record = { LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'], TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'], - GET_STARTED_V2: ['ADMIN', 'EDITOR', 'VIEWER'], + ONBOARDING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_APPLICATION_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_INFRASTRUCTURE_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_LOGS_MANAGEMENT: ['ADMIN', 'EDITOR', 'VIEWER'], From c49a9dac1a544241bdc23c6f56011842849accea Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 28 Oct 2024 17:30:11 +0530 Subject: [PATCH 10/52] feat: feedback updates --- .../api/preferences/updateOrgPreference.ts | 9 +- .../AboutSigNozQuestions.tsx | 8 +- .../InviteTeamMembers/InviteTeamMembers.tsx | 2 +- .../OnboardingFooter.styles.scss | 3 + .../OnboardingFooter/OnboardingFooter.tsx | 26 +--- .../OnboardingHeader.styles.scss | 1 + .../OnboardingHeader/OnboardingHeader.tsx | 11 +- .../OnboardingQuestionaire.styles.scss | 10 ++ .../OptimiseSignozNeeds.tsx | 14 +- .../OrgQuestions/OrgQuestions.tsx | 10 +- .../OnboardingQuestionaire/index.tsx | 146 +++++++++++++----- frontend/src/container/SideNav/SideNav.tsx | 4 +- frontend/src/pages/SignUp/SignUp.tsx | 2 +- .../api/preferences/userOrgPreferences.ts | 4 +- 14 files changed, 160 insertions(+), 90 deletions(-) diff --git a/frontend/src/api/preferences/updateOrgPreference.ts b/frontend/src/api/preferences/updateOrgPreference.ts index 76e5a68640..aae4d83ddf 100644 --- a/frontend/src/api/preferences/updateOrgPreference.ts +++ b/frontend/src/api/preferences/updateOrgPreference.ts @@ -10,9 +10,12 @@ const updateOrgPreference = async ( ): Promise< SuccessResponse | ErrorResponse > => { - const response = await axios.put(`/org/preferences`, { - preference_value: preferencePayload.value, - }); + const response = await axios.put( + `/org/preferences/${preferencePayload.preferenceID}`, + { + preference_value: preferencePayload.value, + }, + ); return { statusCode: 200, diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index df8066b358..c6f0320d1a 100644 --- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -22,7 +22,7 @@ interface AboutSigNozQuestionsProps { } const hearAboutSignozOptions: Record = { - blog: 'Blog', + search: 'Google / Search', hackerNews: 'Hacker News', linkedin: 'LinkedIn', twitter: 'Twitter', @@ -144,7 +144,7 @@ export function AboutSigNozQuestions({
-
- What are you interested in doing with SigNoz? -
+
What got you interested in SigNoz?
{Object.keys(interestedInOptions).map((option: string) => (
); diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss index 0b2cecaac6..984b89828b 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss @@ -40,6 +40,7 @@ border-radius: 2px; border: 1px solid var(--Greyscale-Slate-400, #1d212d); background: var(--Ink-300, #16181d); + box-shadow: none; } .header-container .get-help-container img { diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx index 76801d7620..f1b54d50dd 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx @@ -1,19 +1,26 @@ import './OnboardingHeader.styles.scss'; import { Color } from '@signozhq/design-tokens'; +import { Button } from 'antd'; import { LifeBuoy } from 'lucide-react'; export function OnboardingHeader(): JSX.Element { + const handleGetHelpClick = (): void => { + if (window.Intercom) { + window.Intercom('showNewMessage', ''); + } + }; + return (
SigNoz SigNoz
-
+
+
); } diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index 00b091d1d8..d50ca325b7 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -386,3 +386,13 @@ margin-top: 12px; } } + +.onboarding-questionaire-loading-container { + width: 100%; + display: flex; + height: 100vh; + max-width: 600px; + justify-content: center; + align-items: center; + margin: 0 auto; +} diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index d564158059..a801c2784e 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -16,10 +16,11 @@ interface OptimiseSignozNeedsProps { onBack: () => void; onWillDoLater: () => void; isUpdatingProfile: boolean; + isNextDisabled: boolean; } const logMarks: SliderSingleProps['marks'] = { - 0: '2 GB', + 0: '0 GB', 25: '25 GB', 50: '50 GB', 100: '100 GB', @@ -50,15 +51,16 @@ function OptimiseSignozNeeds({ onNext, onBack, onWillDoLater, + isNextDisabled, }: OptimiseSignozNeedsProps): JSX.Element { const [logsPerDay, setLogsPerDay] = useState( - optimiseSignozDetails?.logsPerDay || 25, + optimiseSignozDetails?.logsPerDay || 0, ); const [hostsPerDay, setHostsPerDay] = useState( - optimiseSignozDetails?.hostsPerDay || 40, + optimiseSignozDetails?.hostsPerDay || 0, ); const [services, setServices] = useState( - optimiseSignozDetails?.services || 10, + optimiseSignozDetails?.services || 0, ); useEffect(() => { @@ -191,7 +193,7 @@ function OptimiseSignozNeeds({ type="primary" className="next-button" onClick={handleOnNext} - disabled={isUpdatingProfile} + disabled={isUpdatingProfile || isNextDisabled} > Next{' '} {isUpdatingProfile ? ( @@ -204,7 +206,7 @@ function OptimiseSignozNeeds({
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index a1fea0aaf8..ea6bad53d6 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -42,10 +42,10 @@ const observabilityTools = { }; const o11yFamiliarityOptions: Record = { - new: "I'm completely new", - builtStack: "I've built a stack before", - experienced: 'I have some experience', - dontKnow: "I don't know what it is", + beginner: 'Basic Understanding', + intermediate: 'Somewhat Familiar', + expert: 'Very Familiar', + notFamiliar: "I'm not familiar with it", }; function OrgQuestions({ @@ -254,7 +254,7 @@ function OrgQuestions({
- Are you familiar with observability (o11y)? + Are you familiar with setting upobservability (o11y)?
{Object.keys(o11yFamiliarityOptions).map((option: string) => ( diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index a5d1f83254..77bde17848 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -1,7 +1,10 @@ import './OnboardingQuestionaire.styles.scss'; +import { Skeleton } from 'antd'; import { NotificationInstance } from 'antd/es/notification/interface'; import updateProfileAPI from 'api/onboarding/updateProfile'; +import getOrgPreference from 'api/preferences/getOrgPreference'; +import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference'; import editOrg from 'api/user/editOrg'; import getOrgUser from 'api/user/getOrgUser'; import { AxiosError } from 'axios'; @@ -10,6 +13,7 @@ import ROUTES from 'constants/routes'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; +import { isEmpty } from 'lodash-es'; import { Dispatch, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from 'react-query'; @@ -56,9 +60,9 @@ const INITIAL_SIGNOZ_DETAILS: SignozDetails = { }; const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { - logsPerDay: 25, - hostsPerDay: 40, - services: 10, + logsPerDay: 0, + hostsPerDay: 0, + services: 0, }; function OnboardingQuestionaire(): JSX.Element { @@ -92,6 +96,44 @@ function OnboardingQuestionaire(): JSX.Element { const dispatch = useDispatch>(); const [orgData, setOrgData] = useState(null); + const [isOnboardingComplete, setIsOnboardingComplete] = useState( + false, + ); + + const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ + queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }), + queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'], + }); + + useEffect(() => { + if (!isLoadingOrgPreferences && !isEmpty(orgPreferences?.payload?.data)) { + const preferenceId = orgPreferences?.payload?.data?.preference_id; + const preferenceValue = orgPreferences?.payload?.data?.preference_value; + + if (preferenceId === 'ORG_ONBOARDING') { + setIsOnboardingComplete(preferenceValue as boolean); + } + } + }, [orgPreferences, isLoadingOrgPreferences]); + + const checkFirstTimeUser = (): boolean => { + const users = orgUsers?.payload || []; + + const remainingUsers = users.filter( + (user) => user.email !== 'admin@signoz.cloud', + ); + + return remainingUsers.length === 1; + }; + + useEffect(() => { + const isFirstUser = checkFirstTimeUser(); + + if (isOnboardingComplete || !isFirstUser) { + history.push(ROUTES.GET_STARTED); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isOnboardingComplete, orgUsers]); useEffect(() => { if (org) { @@ -105,6 +147,11 @@ function OnboardingQuestionaire(): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, [org]); + const isNextDisabled = + optimiseSignozDetails.logsPerDay === 0 || + optimiseSignozDetails.hostsPerDay === 0 || + optimiseSignozDetails.services === 0; + const [isLoading, setIsLoading] = useState(false); const handleOrgNameUpdate = async (): Promise => { @@ -164,9 +211,7 @@ function OnboardingQuestionaire(): JSX.Element { const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation( updateProfileAPI, { - onSuccess: (data) => { - console.log('data', data); - + onSuccess: () => { setCurrentStep(4); }, onError: (error) => { @@ -175,6 +220,15 @@ function OnboardingQuestionaire(): JSX.Element { }, ); + const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, { + onSuccess: () => { + setIsOnboardingComplete(true); + }, + onError: (error) => { + showErrorNotification(notifications, error as AxiosError); + }, + }); + const handleUpdateProfile = (): void => { updateProfile({ familiarity_with_observability: orgDetails?.familiarity as string, @@ -200,7 +254,10 @@ function OnboardingQuestionaire(): JSX.Element { }; const handleOnboardingComplete = (): void => { - history.push(ROUTES.GET_STARTED); + updateOrgPreference({ + preferenceID: 'ORG_ONBOARDING', + value: true, + }); }; return ( @@ -210,42 +267,53 @@ function OnboardingQuestionaire(): JSX.Element {
- {currentStep === 1 && ( - + {(isLoadingOrgPreferences || isLoadingOrgUsers) && ( +
+ +
)} - {currentStep === 2 && ( - setCurrentStep(1)} - onNext={(): void => setCurrentStep(3)} - /> - )} + {!isLoadingOrgPreferences && !isLoadingOrgUsers && ( + <> + {currentStep === 1 && ( + + )} - {currentStep === 3 && ( - 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 - /> - )} + {currentStep === 2 && ( + setCurrentStep(1)} + onNext={(): void => setCurrentStep(3)} + /> + )} - {currentStep === 4 && ( - setCurrentStep(3)} - onNext={handleOnboardingComplete} - /> + {currentStep === 3 && ( + 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 + /> + )} + + {currentStep === 4 && ( + setCurrentStep(3)} + onNext={handleOnboardingComplete} + /> + )} + )}
diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 16787bc3d8..8ccafacb0a 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -113,7 +113,9 @@ function SideNav({ if (!isOnboardingEnabled || !isCloudUser()) { let items = [...menuItems]; - items = items.filter((item) => item.key !== ROUTES.GET_STARTED); + items = items.filter( + (item) => item.key !== ROUTES.GET_STARTED && item.key !== ROUTES.ONBOARDING, + ); setMenuItems(items); } diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 4917b0fe2d..68f9c19dd1 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -261,7 +261,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { values, async (): Promise => { if (isOnboardingEnabled && isCloudUser()) { - history.push(ROUTES.GET_STARTED); + history.push(ROUTES.ONBOARDING); } else { history.push(ROUTES.APPLICATION); } diff --git a/frontend/src/types/api/preferences/userOrgPreferences.ts b/frontend/src/types/api/preferences/userOrgPreferences.ts index 6faa75d5f1..2c77090769 100644 --- a/frontend/src/types/api/preferences/userOrgPreferences.ts +++ b/frontend/src/types/api/preferences/userOrgPreferences.ts @@ -19,12 +19,12 @@ export interface GetAllUserPreferencesResponseProps { } export interface UpdateOrgPreferenceProps { - key: string; + preferenceID: string; value: unknown; } export interface UpdateUserPreferenceProps { - key: string; + preferenceID: string; value: unknown; } From 5c02250aae9d6bacd2b1ad5f59edf02286e216c9 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 28 Oct 2024 18:33:56 +0530 Subject: [PATCH 11/52] feat: feedback updates --- .../AboutSigNozQuestions.tsx | 4 +- .../OnboardingHeader/OnboardingHeader.tsx | 14 -- .../OptimiseSignozNeeds.tsx | 194 +++++++++++++----- .../OrgQuestions/OrgQuestions.tsx | 8 +- .../OnboardingQuestionaire/index.tsx | 9 +- 5 files changed, 152 insertions(+), 77 deletions(-) diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index c6f0320d1a..8ebddd3430 100644 --- a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx @@ -33,7 +33,7 @@ const hearAboutSignozOptions: Record = { const interestedInOptions: Record = { savingCosts: 'Saving costs', otelNativeStack: 'Interested in Otel-native stack', - allInOne: 'All in one', + allInOne: 'All in one (Logs, Metrics & Traces)', }; export function AboutSigNozQuestions({ @@ -144,7 +144,7 @@ export function AboutSigNozQuestions({ { - if (window.Intercom) { - window.Intercom('showNewMessage', ''); - } - }; - return (
SigNoz SigNoz
-
); } diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index a801c2784e..b3fdb9f4e7 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -1,4 +1,4 @@ -import { Button, Slider, SliderSingleProps, Typography } from 'antd'; +import { Button, Slider, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; import { ArrowLeft, ArrowRight, Loader2, Minus } from 'lucide-react'; import { useEffect, useState } from 'react'; @@ -9,6 +9,28 @@ export interface OptimiseSignozDetails { services: number; } +// Define exponential range +const logsMin = 1; // Set to your minimum value in the exponential range +const logsMax = 10000; // Set to your maximum value in the exponential range + +const hostsMin = 1; +const hostsMax = 10000; + +const servicesMin = 1; +const servicesMax = 5000; + +// Function to convert linear slider value to exponential scale +const linearToExponential = ( + value: number, + min: number, + max: number, +): number => { + const expMin = Math.log10(min); + const expMax = Math.log10(max); + const expValue = 10 ** (expMin + ((expMax - expMin) * value) / 100); + return Math.round(expValue); +}; + interface OptimiseSignozNeedsProps { optimiseSignozDetails: OptimiseSignozDetails; setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void; @@ -19,29 +41,28 @@ interface OptimiseSignozNeedsProps { isNextDisabled: boolean; } -const logMarks: SliderSingleProps['marks'] = { - 0: '0 GB', - 25: '25 GB', - 50: '50 GB', - 100: '100 GB', +const marks = { + 0: `${linearToExponential(0, logsMin, logsMax).toLocaleString()} GB`, + 25: `${linearToExponential(25, logsMin, logsMax).toLocaleString()} GB`, + 50: `${linearToExponential(50, logsMin, logsMax).toLocaleString()} GB`, + 75: `${linearToExponential(75, logsMin, logsMax).toLocaleString()} GB`, + 100: `${linearToExponential(100, logsMin, logsMax).toLocaleString()} GB`, }; -const hostMarks: SliderSingleProps['marks'] = { - 0: '0', - 20: '20', - 40: '40', - 60: '60', - 80: '80', - 100: '100', +const hostMarks = { + 0: `${linearToExponential(0, hostsMin, hostsMax).toLocaleString()}`, + 25: `${linearToExponential(25, hostsMin, hostsMax).toLocaleString()}`, + 50: `${linearToExponential(50, hostsMin, hostsMax).toLocaleString()}`, + 75: `${linearToExponential(75, hostsMin, hostsMax).toLocaleString()}`, + 100: `${linearToExponential(100, hostsMin, hostsMax).toLocaleString()}`, }; -const serviceMarks: SliderSingleProps['marks'] = { - 0: '0', - 20: '20', - 40: '40', - 60: '60', - 80: '80', - 100: '100', +const serviceMarks = { + 0: `${linearToExponential(0, servicesMin, servicesMax).toLocaleString()}`, + 25: `${linearToExponential(25, servicesMin, servicesMax).toLocaleString()}`, + 50: `${linearToExponential(50, servicesMin, servicesMax).toLocaleString()}`, + 75: `${linearToExponential(75, servicesMin, servicesMax).toLocaleString()}`, + 100: `${linearToExponential(100, servicesMin, servicesMax).toLocaleString()}`, }; function OptimiseSignozNeeds({ @@ -108,6 +129,52 @@ function OptimiseSignozNeeds({ }); }; + // Internal state for the linear slider + const [sliderValues, setSliderValues] = useState({ + logsPerDay: 0, + hostsPerDay: 0, + services: 0, + }); + + const handleSliderChange = (key: string, value: number): void => { + console.log('value', value); + setSliderValues({ + ...sliderValues, + [key]: value, + }); + + switch (key) { + case 'logsPerDay': + setLogsPerDay(value); + break; + case 'hostsPerDay': + setHostsPerDay(value); + break; + case 'services': + setServices(value); + break; + default: + break; + } + }; + + // Calculate the exponential value based on the current slider position + const logsPerDayValue = linearToExponential( + sliderValues.logsPerDay, + logsMin, + logsMax, + ); + const hostsPerDayValue = linearToExponential( + sliderValues.hostsPerDay, + hostsMin, + hostsMax, + ); + const servicesValue = linearToExponential( + sliderValues.services, + servicesMin, + servicesMax, + ); + return (
@@ -128,16 +195,25 @@ function OptimiseSignozNeeds({ Logs / Day
- setLogsPerDay(value)} - styles={{ - track: { - background: '#4E74F8', - }, - }} - /> +
+ + handleSliderChange('logsPerDay', value) + } + styles={{ + track: { + background: '#4E74F8', + }, + }} + tooltip={{ + formatter: (): string => `${logsPerDayValue.toLocaleString()} GB`, // Show whole number + }} + /> +
@@ -146,16 +222,25 @@ function OptimiseSignozNeeds({ Metrics Number of Hosts
- setHostsPerDay(value)} - styles={{ - track: { - background: '#4E74F8', - }, - }} - /> +
+ + handleSliderChange('hostsPerDay', value) + } + styles={{ + track: { + background: '#4E74F8', + }, + }} + tooltip={{ + formatter: (): string => `${hostsPerDayValue.toLocaleString()}`, // Show whole number + }} + /> +
@@ -164,16 +249,25 @@ function OptimiseSignozNeeds({ Number of services
- setServices(value)} - styles={{ - track: { - background: '#4E74F8', - }, - }} - /> +
+ + handleSliderChange('services', value) + } + styles={{ + track: { + background: '#4E74F8', + }, + }} + tooltip={{ + formatter: (): string => `${servicesValue.toLocaleString()}`, // Show whole number + }} + /> +
diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index ea6bad53d6..17822c2342 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -42,9 +42,9 @@ const observabilityTools = { }; const o11yFamiliarityOptions: Record = { - beginner: 'Basic Understanding', - intermediate: 'Somewhat Familiar', - expert: 'Very Familiar', + beginner: 'Beginner', + intermediate: 'Intermediate', + expert: 'Expert', notFamiliar: "I'm not familiar with it", }; @@ -254,7 +254,7 @@ function OrgQuestions({
- Are you familiar with setting upobservability (o11y)? + Are you familiar with setting up observability (o11y)?
{Object.keys(o11yFamiliarityOptions).map((option: string) => ( diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 77bde17848..ade99c099f 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -28,7 +28,6 @@ import { SignozDetails, } from './AboutSigNozQuestions/AboutSigNozQuestions'; import InviteTeamMembers from './InviteTeamMembers/InviteTeamMembers'; -import { OnboardingFooter } from './OnboardingFooter/OnboardingFooter'; import { OnboardingHeader } from './OnboardingHeader/OnboardingHeader'; import OptimiseSignozNeeds, { OptimiseSignozDetails, @@ -148,8 +147,8 @@ function OnboardingQuestionaire(): JSX.Element { }, [org]); const isNextDisabled = - optimiseSignozDetails.logsPerDay === 0 || - optimiseSignozDetails.hostsPerDay === 0 || + optimiseSignozDetails.logsPerDay === 0 && + optimiseSignozDetails.hostsPerDay === 0 && optimiseSignozDetails.services === 0; const [isLoading, setIsLoading] = useState(false); @@ -316,10 +315,6 @@ function OnboardingQuestionaire(): JSX.Element { )}
- -
- -
); } From 42ac9ab6fe6706bb8ee5cd29799dcc20d864b366 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 28 Oct 2024 20:09:08 +0530 Subject: [PATCH 12/52] feat: update to use v2 instance --- frontend/src/api/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index a340f03ff1..b3a810e2ef 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -175,12 +175,12 @@ export const GatewayApiV2Instance = axios.create({ baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV2}`, }); -GatewayApiV1Instance.interceptors.response.use( +GatewayApiV2Instance.interceptors.response.use( interceptorsResponse, interceptorRejected, ); -GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse); +GatewayApiV2Instance.interceptors.request.use(interceptorsRequestResponse); // AxiosAlertManagerInstance.interceptors.response.use( From 44f41c55f9d80fd0ebfbd9fd24713eee81a8f357 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 28 Oct 2024 21:01:01 +0530 Subject: [PATCH 13/52] feat: handle linear to exponential conversion for logs, services and hosts --- .../OptimiseSignozNeeds.tsx | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index b3fdb9f4e7..d54b40b702 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -31,6 +31,18 @@ const linearToExponential = ( return Math.round(expValue); }; +const exponentialToLinear = ( + expValue: number, + min: number, + max: number, +): number => { + const expMin = Math.log10(min); + const expMax = Math.log10(max); + const linearValue = + ((Math.log10(expValue) - expMin) / (expMax - expMin)) * 100; + return Math.round(linearValue); // Round to get a whole number within the 0-100 range +}; + interface OptimiseSignozNeedsProps { optimiseSignozDetails: OptimiseSignozDetails; setOptimiseSignozDetails: (details: OptimiseSignozDetails) => void; @@ -84,6 +96,22 @@ function OptimiseSignozNeeds({ optimiseSignozDetails?.services || 0, ); + // Internal state for the linear slider + const [sliderValues, setSliderValues] = useState({ + logsPerDay: 0, + hostsPerDay: 0, + services: 0, + }); + + useEffect(() => { + setSliderValues({ + logsPerDay: exponentialToLinear(logsPerDay, logsMin, logsMax), + hostsPerDay: exponentialToLinear(hostsPerDay, hostsMin, hostsMax), + services: exponentialToLinear(services, servicesMin, servicesMax), + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { setOptimiseSignozDetails({ logsPerDay, @@ -129,15 +157,7 @@ function OptimiseSignozNeeds({ }); }; - // Internal state for the linear slider - const [sliderValues, setSliderValues] = useState({ - logsPerDay: 0, - hostsPerDay: 0, - services: 0, - }); - const handleSliderChange = (key: string, value: number): void => { - console.log('value', value); setSliderValues({ ...sliderValues, [key]: value, @@ -145,13 +165,13 @@ function OptimiseSignozNeeds({ switch (key) { case 'logsPerDay': - setLogsPerDay(value); + setLogsPerDay(linearToExponential(value, logsMin, logsMax)); break; case 'hostsPerDay': - setHostsPerDay(value); + setHostsPerDay(linearToExponential(value, hostsMin, hostsMax)); break; case 'services': - setServices(value); + setServices(linearToExponential(value, servicesMin, servicesMax)); break; default: break; From a1090bfdc5999fb27bcba3f02751354fd1cb0b20 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Tue, 29 Oct 2024 14:20:18 +0530 Subject: [PATCH 14/52] feat: handle invite user flows --- frontend/src/api/user/inviteUsers.ts | 6 +- .../AboutSigNozQuestions.tsx | 9 +- .../InviteTeamMembers.styles.scss | 47 ++++- .../InviteTeamMembers/InviteTeamMembers.tsx | 197 +++++++++++++++--- .../OnboardingQuestionaire.styles.scss | 1 - .../OptimiseSignozNeeds.tsx | 10 +- .../OrgQuestions/OrgQuestions.tsx | 2 +- .../OnboardingQuestionaire/index.tsx | 37 +++- frontend/src/types/api/user/inviteUsers.ts | 24 +++ 9 files changed, 270 insertions(+), 63 deletions(-) diff --git a/frontend/src/api/user/inviteUsers.ts b/frontend/src/api/user/inviteUsers.ts index 28189159ff..d7afb7ff53 100644 --- a/frontend/src/api/user/inviteUsers.ts +++ b/frontend/src/api/user/inviteUsers.ts @@ -1,10 +1,10 @@ import axios from 'api'; -import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps, UsersProps } from 'types/api/user/inviteUsers'; +import { SuccessResponse } from 'types/api'; +import { InviteUsersResponse, UsersProps } from 'types/api/user/inviteUsers'; const inviteUsers = async ( users: UsersProps, -): Promise | ErrorResponse> => { +): Promise> => { const response = await axios.post(`/invite/bulk`, users); return { diff --git a/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/AboutSigNozQuestions/AboutSigNozQuestions.tsx index 8ebddd3430..ee7606ff3f 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('Onboarding: SigNoz Questions: Next', { + logEvent('User Onboarding: About SigNoz Questions Answered', { hearAboutSignoz, otherAboutSignoz, interestInSignoz, @@ -100,13 +100,6 @@ export function AboutSigNozQuestions({ otherInterestInSignoz, }); - logEvent('Onboarding: SigNoz Questions: Back', { - hearAboutSignoz, - otherAboutSignoz, - interestInSignoz, - otherInterestInSignoz, - }); - onBack(); }; diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss index ae26562b3a..1d1be4b0df 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -29,24 +29,57 @@ } .questions-form-container { - .error-message-container { - padding: 16px; - margin-top: 16px; + .error-message-container, + .success-message-container, + .partially-sent-invites-container { border-radius: 4px; - border: 1px solid var(--bg-slate-500, #161922); - background: var(--bg-ink-400, #121317); width: 100%; display: flex; align-items: center; - .error-message { + .error-message, + .success-message { + font-size: 12px; + font-weight: 400; + + display: flex; + align-items: center; + gap: 8px; + } + } + + .invite-users-error-message-container, + .invite-users-success-message-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + + .success-message { + color: var(--bg-success-500, #00b37e); + } + } + + .partially-sent-invites-container { + margin-top: 16px; + padding: 8px; + border: 1px solid #1d212d; + background-color: #121317; + + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + + .partially-sent-invites-message { + color: var(--bg-warning-500, #fbbd23); + font-size: 12px; font-weight: 400; display: flex; align-items: center; gap: 8px; - text-transform: capitalize; } } } diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index f9d7fd3ae0..a316ee34d2 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -2,6 +2,7 @@ import './InviteTeamMembers.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Select, Typography } from 'antd'; +import logEvent from 'api/common/logEvent'; import inviteUsers from 'api/user/inviteUsers'; import { AxiosError } from 'axios'; import { cloneDeep, debounce, isEmpty } from 'lodash-es'; @@ -15,7 +16,12 @@ import { } from 'lucide-react'; import { useCallback, useEffect, useState } from 'react'; import { useMutation } from 'react-query'; -import { ErrorResponse } from 'types/api'; +import { SuccessResponse } from 'types/api'; +import { + FailedInvite, + InviteUsersResponse, + SuccessfulInvite, +} from 'types/api/user/inviteUsers'; import { v4 as uuid } from 'uuid'; interface TeamMember { @@ -46,8 +52,23 @@ function InviteTeamMembers({ {}, ); const [hasInvalidEmails, setHasInvalidEmails] = useState(false); + + const [hasErrors, setHasErrors] = useState(true); + const [error, setError] = useState(null); + const [inviteUsersErrorResponse, setInviteUsersErrorResponse] = useState< + string[] | null + >(null); + + const [inviteUsersSuccessResponse, setInviteUsersSuccessResponse] = useState< + string[] | null + >(null); + + const [disableNextButton, setDisableNextButton] = useState(false); + + const [allInvitesSent, setAllInvitesSent] = useState(false); + const defaultTeamMember: TeamMember = { email: '', role: 'EDITOR', @@ -100,30 +121,100 @@ function InviteTeamMembers({ return isValid; }; - const handleError = (error: AxiosError): void => { - const errorMessage = error.response?.data as ErrorResponse; + const parseInviteUsersSuccessResponse = ( + response: SuccessfulInvite[], + ): string[] => response.map((invite) => `${invite.email} - Invite Sent`); - setError(errorMessage.error); + const parseInviteUsersErrorResponse = (response: FailedInvite[]): string[] => + response.map((invite) => `${invite.email} - ${invite.error}`); + + const handleError = (error: AxiosError): void => { + const errorMessage = error.response?.data as InviteUsersResponse; + + if (errorMessage?.status === 'failure') { + setHasErrors(true); + + const failedInvitesErrorResponse = parseInviteUsersErrorResponse( + errorMessage.failed_invites, + ); + + setInviteUsersErrorResponse(failedInvitesErrorResponse); + } }; - const { mutate: sendInvites, isLoading: isSendingInvites } = useMutation( - inviteUsers, - { - onSuccess: (): void => { + const handleInviteUsersSuccess = ( + response: SuccessResponse, + ): void => { + const inviteUsersResponse = response.payload as InviteUsersResponse; + + if (inviteUsersResponse?.status === 'success') { + const successfulInvites = parseInviteUsersSuccessResponse( + inviteUsersResponse.successful_invites, + ); + + setDisableNextButton(true); + + setError(null); + setHasErrors(false); + setInviteUsersErrorResponse(null); + setAllInvitesSent(true); + + setInviteUsersSuccessResponse(successfulInvites); + + setTimeout(() => { + setDisableNextButton(false); onNext(); - }, - onError: (error): void => { - handleError(error as AxiosError); - }, + }, 1000); + } else if (inviteUsersResponse?.status === 'partial_success') { + const successfulInvites = parseInviteUsersSuccessResponse( + inviteUsersResponse.successful_invites, + ); + + setInviteUsersSuccessResponse(successfulInvites); + + if (inviteUsersResponse.failed_invites.length > 0) { + setHasErrors(true); + + setInviteUsersErrorResponse( + parseInviteUsersErrorResponse(inviteUsersResponse.failed_invites), + ); + } + } + }; + + const { + mutate: sendInvites, + isLoading: isSendingInvites, + data: inviteUsersApiResponseData, + } = useMutation(inviteUsers, { + onSuccess: (response: SuccessResponse): void => { + logEvent('User Onboarding: Invite Team Members Sent', { + teamMembers: teamMembersToInvite, + }); + + handleInviteUsersSuccess(response); }, - ); + onError: (error: AxiosError): void => { + console.log('error', error); + + logEvent('User Onboarding: Invite Team Members Failed', { + teamMembers: teamMembersToInvite, + error, + }); + + handleError(error); + }, + }); const handleNext = (): void => { if (validateAllUsers()) { setTeamMembers(teamMembersToInvite || []); - setError(null); setHasInvalidEmails(false); + setError(null); + setHasErrors(false); + setInviteUsersErrorResponse(null); + setInviteUsersSuccessResponse(null); sendInvites({ users: teamMembersToInvite || [], @@ -165,6 +256,11 @@ function InviteTeamMembers({ }; const handleDoLater = (): void => { + logEvent('User Onboarding: Invite Team Members Skipped', { + teamMembers: teamMembersToInvite, + apiResponse: inviteUsersApiResponseData, + }); + onNext(); }; @@ -246,21 +342,65 @@ function InviteTeamMembers({
+ + {hasInvalidEmails && ( +
+ + Please enter valid emails for all team + members + +
+ )} + + {error && ( +
+ + {error} + +
+ )} + + {inviteUsersSuccessResponse && ( +
+ {inviteUsersSuccessResponse?.map((success, index) => ( + + {success} + + ))} +
+ )} + + {hasErrors && ( +
+ {inviteUsersErrorResponse?.map((error, index) => ( + + {error} + + ))} +
+ )}
- {hasInvalidEmails && ( -
- - Please enter valid emails for all team - members + {/* Partially sent invites */} + {inviteUsersSuccessResponse && inviteUsersErrorResponse && ( +
+ + + Some invites were sent successfully. Please fix the errors above and + resend invites. -
- )} - {error && ( -
- - {error} + + You can click on I'll do this later to go to next step.
)} @@ -275,10 +415,11 @@ function InviteTeamMembers({ type="primary" className="next-button" onClick={handleNext} - loading={isSendingInvites} + loading={isSendingInvites || disableNextButton} > - Send Invites - + {allInvitesSent ? 'Invites Sent' : 'Send Invites'} + + {allInvitesSent ? : }
diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index d50ca325b7..5737379d40 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -90,7 +90,6 @@ .invite-team-members-container { max-height: 260px; - padding-right: 8px; overflow-y: auto; &::-webkit-scrollbar { diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index d54b40b702..08177c1e27 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('Onboarding: Optimise SigNoz Needs: Next', { + logEvent('User Onboarding: Optimise SigNoz Needs Answered', { logsPerDay, hostsPerDay, services, @@ -132,12 +132,6 @@ function OptimiseSignozNeeds({ }; const handleOnBack = (): void => { - logEvent('Onboarding: Optimise SigNoz Needs: Back', { - logsPerDay, - hostsPerDay, - services, - }); - onBack(); }; @@ -150,7 +144,7 @@ function OptimiseSignozNeeds({ onWillDoLater(); - logEvent('Onboarding: Optimise SigNoz Needs: Will do later', { + logEvent('User Onboarding: Optimise SigNoz Needs Skipped', { logsPerDay: 0, hostsPerDay: 0, services: 0, diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index 17822c2342..ab8f7d4a5c 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -120,7 +120,7 @@ function OrgQuestions({ familiarity, }); - logEvent('Onboarding: Org Questions: Next', { + logEvent('User Onboarding: Org Questions Answered', { organisationName, usesObservability, observabilityTool, diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index ade99c099f..97ebc33269 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -2,6 +2,7 @@ import './OnboardingQuestionaire.styles.scss'; import { Skeleton } from 'antd'; import { NotificationInstance } from 'antd/es/notification/interface'; +import logEvent from 'api/common/logEvent'; import updateProfileAPI from 'api/onboarding/updateProfile'; import getOrgPreference from 'api/preferences/getOrgPreference'; import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference'; @@ -67,7 +68,7 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { function OnboardingQuestionaire(): JSX.Element { const { notifications } = useNotifications(); - const [currentStep, setCurrentStep] = useState(1); + const [currentStep, setCurrentStep] = useState(4); const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); const [signozDetails, setSignozDetails] = useState( INITIAL_SIGNOZ_DETAILS, @@ -91,8 +92,6 @@ function OnboardingQuestionaire(): JSX.Element { queryKey: ['getOrgUser', org?.[0].id], }); - console.log('orgUsers', orgUsers, isLoadingOrgUsers); - const dispatch = useDispatch>(); const [orgData, setOrgData] = useState(null); const [isOnboardingComplete, setIsOnboardingComplete] = useState( @@ -126,13 +125,29 @@ function OnboardingQuestionaire(): JSX.Element { }; useEffect(() => { - const isFirstUser = checkFirstTimeUser(); + // Only run this effect if the org users and preferences are loaded + if (!isLoadingOrgUsers && !isLoadingOrgPreferences) { + const isFirstUser = checkFirstTimeUser(); - if (isOnboardingComplete || !isFirstUser) { - history.push(ROUTES.GET_STARTED); + // Redirect to get started if it's not the first user or if the onboarding is complete + if (!isFirstUser || isOnboardingComplete) { + history.push(ROUTES.GET_STARTED); + + logEvent('User Onboarding: Redirected to Get Started', { + isFirstUser, + isOnboardingComplete, + }); + } else { + logEvent('User Onboarding: Started', {}); + } } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isOnboardingComplete, orgUsers]); + }, [ + isLoadingOrgUsers, + isLoadingOrgPreferences, + isOnboardingComplete, + orgUsers, + ]); useEffect(() => { if (org) { @@ -182,8 +197,16 @@ function OnboardingQuestionaire(): JSX.Element { }, }); + logEvent('User Onboarding: Org Name Updated', { + organisationName: orgDetails.organisationName, + }); + setCurrentStep(2); } else { + logEvent('User Onboarding: Org Name Update Failed', { + organisationName: orgDetails.organisationName, + }); + notifications.error({ message: error || diff --git a/frontend/src/types/api/user/inviteUsers.ts b/frontend/src/types/api/user/inviteUsers.ts index 8491e31cc4..e173ca8f8c 100644 --- a/frontend/src/types/api/user/inviteUsers.ts +++ b/frontend/src/types/api/user/inviteUsers.ts @@ -1,5 +1,7 @@ import { User } from 'types/reducer/app'; +import { ErrorResponse } from '..'; + export interface UserProps { name: User['name']; email: User['email']; @@ -14,3 +16,25 @@ export interface UsersProps { export interface PayloadProps { data: string; } + +export interface FailedInvite { + email: string; + error: string; +} + +export interface SuccessfulInvite { + email: string; + invite_link: string; + status: string; +} + +export interface InviteUsersResponse extends ErrorResponse { + status: string; + summary: { + total_invites: number; + successful_invites: number; + failed_invites: number; + }; + successful_invites: SuccessfulInvite[]; + failed_invites: FailedInvite[]; +} From 845dc00568474b9f1516e402d9c986743c5dec73 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Tue, 29 Oct 2024 19:57:00 +0530 Subject: [PATCH 15/52] feat: handle onboarding visibility --- frontend/src/AppRoutes/Private.tsx | 111 +++++++++++++- frontend/src/AppRoutes/index.tsx | 43 +++++- .../OrgQuestions/OrgQuestions.tsx | 111 +++++++++++--- .../OnboardingQuestionaire/index.tsx | 140 +++++++----------- frontend/src/store/reducers/app.ts | 15 ++ frontend/src/styles.scss | 45 ++++-- frontend/src/types/actions/app.ts | 21 ++- .../api/preferences/userOrgPreferences.ts | 4 +- frontend/src/types/reducer/app.ts | 14 ++ frontend/src/utils/permission/index.ts | 2 +- 10 files changed, 372 insertions(+), 134 deletions(-) diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx index 43402fdbb2..3956676ec7 100644 --- a/frontend/src/AppRoutes/Private.tsx +++ b/frontend/src/AppRoutes/Private.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ import getLocalStorageApi from 'api/browser/localstorage/get'; +import getOrgUser from 'api/user/getOrgUser'; import loginApi from 'api/user/login'; import { Logout } from 'api/utils'; import Spinner from 'components/Spinner'; @@ -8,8 +9,10 @@ import ROUTES from 'constants/routes'; import useLicense from 'hooks/useLicense'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { ReactChild, useEffect, useMemo } from 'react'; +import { isEmpty } from 'lodash-es'; +import { ReactChild, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { matchPath, Redirect, useLocation } from 'react-router-dom'; import { Dispatch } from 'redux'; @@ -17,6 +20,7 @@ import { AppState } from 'store/reducers'; import { getInitialUserTokenRefreshToken } from 'store/utils'; import AppActions from 'types/actions'; import { UPDATE_USER_IS_FETCH } from 'types/actions/app'; +import { Organization } from 'types/api/user/getOrganization'; import AppReducer from 'types/reducer/app'; import { routePermission } from 'utils/permission'; @@ -31,6 +35,14 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { const location = useLocation(); const { pathname } = location; + const { org, orgPreferences } = useSelector( + (state) => state.app, + ); + + const [isOnboardingComplete, setIsOnboardingComplete] = useState< + boolean | null + >(null); + const mapRoutes = useMemo( () => new Map( @@ -44,6 +56,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { [pathname], ); + useEffect(() => { + if (orgPreferences && !isEmpty(orgPreferences)) { + const onboardingPreference = orgPreferences?.find( + (preference: Record) => preference.key === 'ORG_ONBOARDING', + ); + + if (onboardingPreference) { + setIsOnboardingComplete(onboardingPreference.value); + } + } + }, [orgPreferences]); + const { data: licensesData, isFetching: isFetchingLicensesData, @@ -53,9 +77,11 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { isUserFetching, isUserFetchingError, isLoggedIn: isLoggedInState, + isFetchingOrgPreferences, } = useSelector((state) => state.app); const { t } = useTranslation(['common']); + const localStorageUserAuthToken = getInitialUserTokenRefreshToken(); const dispatch = useDispatch>(); @@ -66,6 +92,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { const isOldRoute = oldRoutes.indexOf(pathname) > -1; + const [orgData, setOrgData] = useState(undefined); + const isLocalStorageLoggedIn = getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true'; @@ -81,6 +109,42 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { } }; + const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({ + queryFn: () => { + if (orgData && orgData.id !== undefined) { + return getOrgUser({ + orgId: orgData.id, + }); + } + return undefined; + }, + queryKey: ['getOrgUser'], + enabled: !isEmpty(orgData), + }); + + const checkFirstTimeUser = (): boolean => { + const users = orgUsers?.payload || []; + + const remainingUsers = users.filter( + (user) => user.email !== 'admin@signoz.cloud', + ); + + return remainingUsers.length === 1; + }; + + // Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load + const shouldShowOnboarding = (): boolean => { + // Only run this effect if the org users and preferences are loaded + if (!isLoadingOrgUsers) { + const isFirstUser = checkFirstTimeUser(); + + // Redirect to get started if it's not the first user or if the onboarding is complete + return isFirstUser && !isOnboardingComplete; + } + + return false; + }; + const handleUserLoginIfTokenPresent = async ( key: keyof typeof ROUTES, ): Promise => { @@ -102,6 +166,16 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { response.payload.refreshJwt, ); + const showOnboarding = shouldShowOnboarding(); + + if ( + userResponse && + showOnboarding && + userResponse.payload.role === 'ADMIN' + ) { + history.push(ROUTES.ONBOARDING); + } + if ( userResponse && route && @@ -160,6 +234,35 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { } }, [isFetchingLicensesData]); + useEffect(() => { + if (org && org.length > 0 && org[0].id !== undefined) { + setOrgData(org[0]); + } + }, [org]); + + const handleRouting = (): void => { + const showOnboarding = shouldShowOnboarding(); + + if (showOnboarding) { + history.push(ROUTES.ONBOARDING); + } else { + history.push(ROUTES.APPLICATION); + } + }; + + useEffect(() => { + // Only run this effect if the org users and preferences are loaded + if (!isLoadingOrgUsers && isOnboardingComplete !== null) { + const isFirstUser = checkFirstTimeUser(); + + // Redirect to get started if it's not the first user or if the onboarding is complete + if (isFirstUser && !isOnboardingComplete) { + history.push(ROUTES.ONBOARDING); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoadingOrgUsers, isOnboardingComplete, orgUsers]); + // eslint-disable-next-line sonarjs/cognitive-complexity useEffect(() => { (async (): Promise => { @@ -183,7 +286,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { // no need to fetch the user and make user fetching false if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') { - history.push(ROUTES.APPLICATION); + handleRouting(); } dispatch({ type: UPDATE_USER_IS_FETCH, @@ -195,7 +298,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { } else if (pathname === ROUTES.HOME_PAGE) { // routing to application page over root page if (isLoggedInState) { - history.push(ROUTES.APPLICATION); + handleRouting(); } else { navigateToLoginIfNotLoggedIn(); } @@ -214,7 +317,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { return ; } - if (isUserFetching) { + if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) { return ; } diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 8cb2bf0a8b..cf17fd4db3 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -2,6 +2,7 @@ import { ConfigProvider } from 'antd'; import getLocalStorageApi from 'api/browser/localstorage/get'; import setLocalStorageApi from 'api/browser/localstorage/set'; import logEvent from 'api/common/logEvent'; +import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences'; import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; import { FeatureKeys } from 'constants/features'; @@ -24,12 +25,17 @@ import AlertRuleProvider from 'providers/Alert'; import { DashboardProvider } from 'providers/Dashboard/Dashboard'; import { QueryBuilderProvider } from 'providers/QueryBuilder'; import { Suspense, useEffect, useState } from 'react'; +import { useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { Route, Router, Switch } from 'react-router-dom'; import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app'; +import { + UPDATE_FEATURE_FLAG_RESPONSE, + UPDATE_IS_FETCHING_ORG_PREFERENCES, + UPDATE_ORG_PREFERENCES, +} from 'types/actions/app'; import AppReducer, { User } from 'types/reducer/app'; import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app'; @@ -65,6 +71,30 @@ function App(): JSX.Element { const isPremiumSupportEnabled = useFeatureFlags(FeatureKeys.PREMIUM_SUPPORT)?.active || false; + const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ + queryFn: () => getAllOrgPreferences(), + queryKey: ['getOrgPreferences'], + enabled: isLoggedInState, + }); + + useEffect(() => { + if (orgPreferences && !isLoadingOrgPreferences) { + dispatch({ + type: UPDATE_IS_FETCHING_ORG_PREFERENCES, + payload: { + isFetchingOrgPreferences: false, + }, + }); + + dispatch({ + type: UPDATE_ORG_PREFERENCES, + payload: { + orgPreferences: orgPreferences.payload?.data || null, + }, + }); + } + }, [orgPreferences, dispatch, isLoadingOrgPreferences]); + const featureResponse = useGetFeatureFlag((allFlags) => { dispatch({ type: UPDATE_FEATURE_FLAG_RESPONSE, @@ -182,6 +212,16 @@ function App(): JSX.Element { }, [isLoggedInState, isOnBasicPlan, user]); useEffect(() => { + if (pathname === ROUTES.ONBOARDING) { + window.Intercom('update', { + hide_default_launcher: true, + }); + } else { + window.Intercom('update', { + hide_default_launcher: false, + }); + } + trackPageView(pathname); // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); @@ -204,6 +244,7 @@ function App(): JSX.Element { user, licenseData, isPremiumSupportEnabled, + pathname, ]); useEffect(() => { diff --git a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx index ab8f7d4a5c..e0376a6559 100644 --- a/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OrgQuestions/OrgQuestions.tsx @@ -4,10 +4,15 @@ import '../OnboardingQuestionaire.styles.scss'; import { Color } from '@signozhq/design-tokens'; import { Button, Input, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; +import editOrg from 'api/user/editOrg'; +import { useNotifications } from 'hooks/useNotifications'; import { ArrowRight, CheckCircle, Loader2 } from 'lucide-react'; -import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { Dispatch, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_ORG_NAME } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; export interface OrgData { @@ -25,10 +30,9 @@ export interface OrgDetails { } interface OrgQuestionsProps { - isLoading: boolean; + currentOrgData: OrgData | null; orgDetails: OrgDetails; - setOrgDetails: (details: OrgDetails) => void; - onNext: () => void; + onNext: (details: OrgDetails) => void; } const observabilityTools = { @@ -49,12 +53,15 @@ const o11yFamiliarityOptions: Record = { }; function OrgQuestions({ - isLoading, + currentOrgData, orgDetails, - setOrgDetails, onNext, }: OrgQuestionsProps): JSX.Element { const { user } = useSelector((state) => state.app); + const { notifications } = useNotifications(); + const dispatch = useDispatch>(); + + const { t } = useTranslation(['organizationsettings', 'common']); const [organisationName, setOrganisationName] = useState( orgDetails?.organisationName || '', @@ -77,6 +84,78 @@ function OrgQuestions({ setOrganisationName(orgDetails.organisationName); }, [orgDetails.organisationName]); + const [isLoading, setIsLoading] = useState(false); + + const handleOrgNameUpdate = async (): Promise => { + /* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */ + if ( + !currentOrgData || + !organisationName || + organisationName === '' || + orgDetails.organisationName === organisationName + ) { + onNext({ + organisationName, + usesObservability, + observabilityTool, + otherTool, + familiarity, + }); + + return; + } + + try { + setIsLoading(true); + const { statusCode, error } = await editOrg({ + isAnonymous: currentOrgData.isAnonymous, + name: organisationName, + orgId: currentOrgData.id, + }); + if (statusCode === 200) { + dispatch({ + type: UPDATE_ORG_NAME, + payload: { + orgId: currentOrgData?.id, + name: orgDetails.organisationName, + }, + }); + + logEvent('User Onboarding: Org Name Updated', { + organisationName: orgDetails.organisationName, + }); + + onNext({ + organisationName, + usesObservability, + observabilityTool, + otherTool, + familiarity, + }); + } else { + logEvent('User Onboarding: Org Name Update Failed', { + organisationName: orgDetails.organisationName, + }); + + notifications.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + notifications.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + const isValidUsesObservability = (): boolean => { if (usesObservability === null) { return false; @@ -112,23 +191,7 @@ function OrgQuestions({ ]); const handleOnNext = (): void => { - setOrgDetails({ - organisationName, - usesObservability, - observabilityTool, - otherTool, - familiarity, - }); - - logEvent('User Onboarding: Org Questions Answered', { - organisationName, - usesObservability, - observabilityTool, - otherTool, - familiarity, - }); - - onNext(); + handleOrgNameUpdate(); }; return ( diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 97ebc33269..1917cd7a55 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -4,9 +4,9 @@ import { Skeleton } from 'antd'; 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 getOrgPreference from 'api/preferences/getOrgPreference'; import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference'; -import editOrg from 'api/user/editOrg'; import getOrgUser from 'api/user/getOrgUser'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; @@ -16,12 +16,14 @@ import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; import { isEmpty } from 'lodash-es'; import { Dispatch, useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { useMutation, useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; -import { UPDATE_ORG_NAME } from 'types/actions/app'; +import { + UPDATE_IS_FETCHING_ORG_PREFERENCES, + UPDATE_ORG_PREFERENCES, +} from 'types/actions/app'; import AppReducer from 'types/reducer/app'; import { @@ -67,8 +69,8 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { function OnboardingQuestionaire(): JSX.Element { const { notifications } = useNotifications(); - - const [currentStep, setCurrentStep] = useState(4); + const { org } = useSelector((state) => state.app); + const [currentStep, setCurrentStep] = useState(1); const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); const [signozDetails, setSignozDetails] = useState( INITIAL_SIGNOZ_DETAILS, @@ -81,9 +83,6 @@ function OnboardingQuestionaire(): JSX.Element { InviteTeamMembersProps[] | null >(null); - const { t } = useTranslation(['organizationsettings', 'common']); - const { org } = useSelector((state) => state.app); - const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({ queryFn: () => getOrgUser({ @@ -93,26 +92,57 @@ function OnboardingQuestionaire(): JSX.Element { }); const dispatch = useDispatch>(); - const [orgData, setOrgData] = useState(null); + const [currentOrgData, setCurrentOrgData] = useState(null); const [isOnboardingComplete, setIsOnboardingComplete] = useState( false, ); - const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ + const { + data: onboardingPreferenceData, + isLoading: isLoadingOnboardingPreference, + } = useQuery({ queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }), queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'], }); + const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ + queryFn: () => getAllOrgPreferences(), + queryKey: ['getOrgPreferences'], + enabled: isOnboardingComplete, + }); + useEffect(() => { - if (!isLoadingOrgPreferences && !isEmpty(orgPreferences?.payload?.data)) { - const preferenceId = orgPreferences?.payload?.data?.preference_id; - const preferenceValue = orgPreferences?.payload?.data?.preference_value; + if (orgPreferences && !isLoadingOrgPreferences) { + dispatch({ + type: UPDATE_IS_FETCHING_ORG_PREFERENCES, + payload: { + isFetchingOrgPreferences: false, + }, + }); + + dispatch({ + type: UPDATE_ORG_PREFERENCES, + payload: { + orgPreferences: orgPreferences.payload?.data || null, + }, + }); + } + }, [orgPreferences, dispatch, isLoadingOrgPreferences]); + + useEffect(() => { + if ( + !isLoadingOnboardingPreference && + !isEmpty(onboardingPreferenceData?.payload?.data) + ) { + const preferenceId = onboardingPreferenceData?.payload?.data?.preference_id; + const preferenceValue = + onboardingPreferenceData?.payload?.data?.preference_value; if (preferenceId === 'ORG_ONBOARDING') { setIsOnboardingComplete(preferenceValue as boolean); } } - }, [orgPreferences, isLoadingOrgPreferences]); + }, [onboardingPreferenceData, isLoadingOnboardingPreference]); const checkFirstTimeUser = (): boolean => { const users = orgUsers?.payload || []; @@ -126,7 +156,7 @@ function OnboardingQuestionaire(): JSX.Element { useEffect(() => { // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers && !isLoadingOrgPreferences) { + if (!isLoadingOrgUsers && !isLoadingOnboardingPreference) { const isFirstUser = checkFirstTimeUser(); // Redirect to get started if it's not the first user or if the onboarding is complete @@ -144,14 +174,14 @@ function OnboardingQuestionaire(): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ isLoadingOrgUsers, - isLoadingOrgPreferences, + isLoadingOnboardingPreference, isOnboardingComplete, orgUsers, ]); useEffect(() => { if (org) { - setOrgData(org[0]); + setCurrentOrgData(org[0]); setOrgDetails({ ...orgDetails, @@ -166,70 +196,6 @@ function OnboardingQuestionaire(): JSX.Element { optimiseSignozDetails.hostsPerDay === 0 && optimiseSignozDetails.services === 0; - const [isLoading, setIsLoading] = useState(false); - - const handleOrgNameUpdate = async (): Promise => { - /* Early bailout if orgData is not set or if the organisation name is not set or if the organisation name is empty or if the organisation name is the same as the one in the orgData */ - if ( - !orgData || - !orgDetails.organisationName || - orgDetails.organisationName === '' || - orgData.name === orgDetails.organisationName - ) { - setCurrentStep(2); - - return; - } - - try { - setIsLoading(true); - const { statusCode, error } = await editOrg({ - isAnonymous: orgData?.isAnonymous, - name: orgDetails.organisationName, - orgId: orgData?.id, - }); - if (statusCode === 200) { - dispatch({ - type: UPDATE_ORG_NAME, - payload: { - orgId: orgData?.id, - name: orgDetails.organisationName, - }, - }); - - logEvent('User Onboarding: Org Name Updated', { - organisationName: orgDetails.organisationName, - }); - - setCurrentStep(2); - } else { - logEvent('User Onboarding: Org Name Update Failed', { - organisationName: orgDetails.organisationName, - }); - - notifications.error({ - message: - error || - t('something_went_wrong', { - ns: 'common', - }), - }); - } - setIsLoading(false); - } catch (error) { - setIsLoading(false); - notifications.error({ - message: t('something_went_wrong', { - ns: 'common', - }), - }); - } - }; - - const handleOrgDetailsUpdate = (): void => { - handleOrgNameUpdate(); - }; - const { mutate: updateProfile, isLoading: isUpdatingProfile } = useMutation( updateProfileAPI, { @@ -289,20 +255,22 @@ function OnboardingQuestionaire(): JSX.Element {
- {(isLoadingOrgPreferences || isLoadingOrgUsers) && ( + {(isLoadingOnboardingPreference || isLoadingOrgUsers) && (
)} - {!isLoadingOrgPreferences && !isLoadingOrgUsers && ( + {!isLoadingOnboardingPreference && !isLoadingOrgUsers && ( <> {currentStep === 1 && ( { + setOrgDetails(orgDetails); + setCurrentStep(2); + }} /> )} diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts index bdb80d2565..3ae5df9699 100644 --- a/frontend/src/store/reducers/app.ts +++ b/frontend/src/store/reducers/app.ts @@ -8,10 +8,12 @@ import { UPDATE_CURRENT_ERROR, UPDATE_CURRENT_VERSION, UPDATE_FEATURE_FLAG_RESPONSE, + UPDATE_IS_FETCHING_ORG_PREFERENCES, UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION_ERROR, UPDATE_ORG, UPDATE_ORG_NAME, + UPDATE_ORG_PREFERENCES, UPDATE_USER, UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, UPDATE_USER_FLAG, @@ -59,6 +61,8 @@ const InitialValue: InitialValueTypes = { userFlags: {}, ee: 'Y', setupCompleted: true, + orgPreferences: null, + isFetchingOrgPreferences: true, }; const appReducer = ( @@ -73,6 +77,17 @@ const appReducer = ( }; } + case UPDATE_ORG_PREFERENCES: { + return { ...state, orgPreferences: action.payload.orgPreferences }; + } + + case UPDATE_IS_FETCHING_ORG_PREFERENCES: { + return { + ...state, + isFetchingOrgPreferences: action.payload.isFetchingOrgPreferences, + }; + } + case UPDATE_FEATURE_FLAG_RESPONSE: { return { ...state, diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 130464a980..18db88688f 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -276,26 +276,39 @@ notifications - 2050 } @font-face { - font-family: 'Inter'; - src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype'); - font-weight: 300 700; - font-style: normal; + font-family: 'Inter'; + src: url('../public/fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype'); + font-weight: 300 700; + font-style: normal; } @font-face { - font-family: 'Work Sans'; - src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype'); - font-weight: 500; - font-style: normal; + font-family: 'Work Sans'; + src: url('../public/fonts/WorkSans-VariableFont_wght.ttf') format('truetype'); + font-weight: 500; + font-style: normal; } @font-face { - font-family: 'Space Mono'; - src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; + font-family: 'Space Mono'; + src: url('../public/fonts/SpaceMono-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; } @font-face { - font-family: 'Fira Code'; - src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype'); - font-weight: 300 700; - font-style: normal; + font-family: 'Fira Code'; + src: url('../public/fonts/FiraCode-VariableFont_wght.ttf') format('truetype'); + font-weight: 300 700; + font-style: normal; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; } diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts index 54b1992af2..93a9dac5df 100644 --- a/frontend/src/types/actions/app.ts +++ b/frontend/src/types/actions/app.ts @@ -25,7 +25,10 @@ export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME'; export const UPDATE_ORG = 'UPDATE_ORG'; export const UPDATE_CONFIGS = 'UPDATE_CONFIGS'; export const UPDATE_USER_FLAG = 'UPDATE_USER_FLAG'; +export const UPDATE_ORG_PREFERENCES = 'UPDATE_ORG_PREFERENCES'; export const UPDATE_FEATURE_FLAG_RESPONSE = 'UPDATE_FEATURE_FLAG_RESPONSE'; +export const UPDATE_IS_FETCHING_ORG_PREFERENCES = + 'UPDATE_IS_FETCHING_ORG_PREFERENCES'; export interface LoggedInUser { type: typeof LOGGED_IN; @@ -130,6 +133,20 @@ export interface UpdateFeatureFlag { }; } +export interface UpdateOrgPreferences { + type: typeof UPDATE_ORG_PREFERENCES; + payload: { + orgPreferences: AppReducer['orgPreferences']; + }; +} + +export interface UpdateIsFetchingOrgPreferences { + type: typeof UPDATE_IS_FETCHING_ORG_PREFERENCES; + payload: { + isFetchingOrgPreferences: AppReducer['isFetchingOrgPreferences']; + }; +} + export type AppAction = | LoggedInUser | UpdateAppVersion @@ -143,4 +160,6 @@ export type AppAction = | UpdateOrg | UpdateConfigs | UpdateUserFlag - | UpdateFeatureFlag; + | UpdateFeatureFlag + | UpdateOrgPreferences + | UpdateIsFetchingOrgPreferences; diff --git a/frontend/src/types/api/preferences/userOrgPreferences.ts b/frontend/src/types/api/preferences/userOrgPreferences.ts index 2c77090769..ab9292596f 100644 --- a/frontend/src/types/api/preferences/userOrgPreferences.ts +++ b/frontend/src/types/api/preferences/userOrgPreferences.ts @@ -1,3 +1,5 @@ +import { OrgPreference } from 'types/reducer/app'; + export interface GetOrgPreferenceResponseProps { status: string; data: Record; @@ -10,7 +12,7 @@ export interface GetUserPreferenceResponseProps { export interface GetAllOrgPreferencesResponseProps { status: string; - data: Record; + data: OrgPreference[]; } export interface GetAllUserPreferencesResponseProps { diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts index c51defcfb0..cfc431ab03 100644 --- a/frontend/src/types/reducer/app.ts +++ b/frontend/src/types/reducer/app.ts @@ -15,6 +15,18 @@ export interface User { profilePictureURL: UserPayload['profilePictureURL']; } +export interface OrgPreference { + key: string; + name: string; + description: string; + valueType: string; + defaultValue: boolean; + allowedValues: any[]; + isDiscreteValues: boolean; + allowedScopes: string[]; + value: boolean; +} + export default interface AppReducer { isLoggedIn: boolean; currentVersion: string; @@ -30,6 +42,8 @@ export default interface AppReducer { userFlags: null | UserFlags; ee: 'Y' | 'N'; setupCompleted: boolean; + orgPreferences: OrgPreference[] | null; + isFetchingOrgPreferences: boolean; featureResponse: { data: FeatureFlagPayload[] | null; refetch: QueryObserverBaseResult['refetch']; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts index e58843232a..bd27d04934 100644 --- a/frontend/src/utils/permission/index.ts +++ b/frontend/src/utils/permission/index.ts @@ -86,7 +86,7 @@ export const routePermission: Record = { LOGS_PIPELINES: ['ADMIN', 'EDITOR', 'VIEWER'], TRACE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED: ['ADMIN', 'EDITOR', 'VIEWER'], - ONBOARDING: ['ADMIN', 'EDITOR', 'VIEWER'], + ONBOARDING: ['ADMIN'], GET_STARTED_APPLICATION_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_INFRASTRUCTURE_MONITORING: ['ADMIN', 'EDITOR', 'VIEWER'], GET_STARTED_LOGS_MANAGEMENT: ['ADMIN', 'EDITOR', 'VIEWER'], From 264233867206523f75e5ba00e29810c8a153a1c5 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 30 Oct 2024 12:32:56 +0530 Subject: [PATCH 16/52] chore: update org onboarding package --- frontend/src/AppRoutes/pageComponents.ts | 4 ++-- frontend/src/AppRoutes/routes.ts | 4 ++-- frontend/src/pages/OnboardingPageV2/index.tsx | 3 --- .../OnboardingPageV2.tsx => OrgOnboarding/OrgOnboarding.tsx} | 4 ++-- frontend/src/pages/OrgOnboarding/index.tsx | 3 +++ 5 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 frontend/src/pages/OnboardingPageV2/index.tsx rename frontend/src/pages/{OnboardingPageV2/OnboardingPageV2.tsx => OrgOnboarding/OrgOnboarding.tsx} (68%) create mode 100644 frontend/src/pages/OrgOnboarding/index.tsx diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index c267304e6b..2def2ac11f 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -66,8 +66,8 @@ export const Onboarding = Loadable( () => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'), ); -export const OnboardingV2 = Loadable( - () => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPageV2'), +export const OrgOnboarding = Loadable( + () => import(/* webpackChunkName: "OrgOnboarding" */ 'pages/OrgOnboarding'), ); export const DashboardPage = Loadable( diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 1901095a67..ccbb700387 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -31,8 +31,8 @@ import { NewDashboardPage, OldLogsExplorer, Onboarding, - OnboardingV2, OrganizationSettings, + OrgOnboarding, PasswordReset, PipelinePage, ServiceMapPage, @@ -72,7 +72,7 @@ const routes: AppRoutes[] = [ { path: ROUTES.ONBOARDING, exact: false, - component: OnboardingV2, + component: OrgOnboarding, isPrivate: true, key: 'ONBOARDING', }, diff --git a/frontend/src/pages/OnboardingPageV2/index.tsx b/frontend/src/pages/OnboardingPageV2/index.tsx deleted file mode 100644 index 9fad953dfe..0000000000 --- a/frontend/src/pages/OnboardingPageV2/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import OnboardingPage from './OnboardingPageV2'; - -export default OnboardingPage; diff --git a/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx b/frontend/src/pages/OrgOnboarding/OrgOnboarding.tsx similarity index 68% rename from frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx rename to frontend/src/pages/OrgOnboarding/OrgOnboarding.tsx index 7100894a41..33e1523577 100644 --- a/frontend/src/pages/OnboardingPageV2/OnboardingPageV2.tsx +++ b/frontend/src/pages/OrgOnboarding/OrgOnboarding.tsx @@ -1,6 +1,6 @@ import OnboardingQuestionaire from 'container/OnboardingQuestionaire'; -function OnboardingPageV2(): JSX.Element { +function OrgOnboarding(): JSX.Element { return (
@@ -8,4 +8,4 @@ function OnboardingPageV2(): JSX.Element { ); } -export default OnboardingPageV2; +export default OrgOnboarding; diff --git a/frontend/src/pages/OrgOnboarding/index.tsx b/frontend/src/pages/OrgOnboarding/index.tsx new file mode 100644 index 0000000000..64e413ce30 --- /dev/null +++ b/frontend/src/pages/OrgOnboarding/index.tsx @@ -0,0 +1,3 @@ +import OnboardingPage from './OrgOnboarding'; + +export default OnboardingPage; From 1b33efe4cc21bb5c50a73821647660400256fc1f Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 30 Oct 2024 13:08:43 +0530 Subject: [PATCH 17/52] chore: remove workspace from fullscreen --- frontend/src/container/AppLayout/index.tsx | 1 - .../InviteTeamMembers/InviteTeamMembers.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 700914cc6a..d9beb2d5d3 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -192,7 +192,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const renderFullScreen = pathname === ROUTES.GET_STARTED || pathname === ROUTES.ONBOARDING || - pathname === ROUTES.WORKSPACE_LOCKED || pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING || pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING || pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT || diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index a316ee34d2..0f99dd315b 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -195,8 +195,6 @@ function InviteTeamMembers({ handleInviteUsersSuccess(response); }, onError: (error: AxiosError): void => { - console.log('error', error); - logEvent('User Onboarding: Invite Team Members Failed', { teamMembers: teamMembersToInvite, error, From 8e19c346a45cbe399cefec290f413fa5c38d7106 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 30 Oct 2024 13:40:13 +0530 Subject: [PATCH 18/52] feat: handle light mode and remove unnecessary logos --- frontend/public/Logos/hippa.svg | 10 - .../public/Logos/signoz-brand-logo-new.svg | 30 --- frontend/public/Logos/soc2.svg | 72 ----- frontend/public/fonts/Satoshi-Regular.woff2 | Bin 25516 -> 0 bytes .../InviteTeamMembers.styles.scss | 37 +++ .../OnboardingHeader.styles.scss | 16 +- .../OnboardingHeader/OnboardingHeader.tsx | 2 +- .../OnboardingQuestionaire.styles.scss | 245 ++++++++++++++++-- 8 files changed, 267 insertions(+), 145 deletions(-) delete mode 100644 frontend/public/Logos/hippa.svg delete mode 100644 frontend/public/Logos/signoz-brand-logo-new.svg delete mode 100644 frontend/public/Logos/soc2.svg delete mode 100644 frontend/public/fonts/Satoshi-Regular.woff2 diff --git a/frontend/public/Logos/hippa.svg b/frontend/public/Logos/hippa.svg deleted file mode 100644 index 2a1b74b818..0000000000 --- a/frontend/public/Logos/hippa.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/frontend/public/Logos/signoz-brand-logo-new.svg b/frontend/public/Logos/signoz-brand-logo-new.svg deleted file mode 100644 index f57d3c5def..0000000000 --- a/frontend/public/Logos/signoz-brand-logo-new.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/public/Logos/soc2.svg b/frontend/public/Logos/soc2.svg deleted file mode 100644 index 234321c95c..0000000000 --- a/frontend/public/Logos/soc2.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/public/fonts/Satoshi-Regular.woff2 b/frontend/public/fonts/Satoshi-Regular.woff2 deleted file mode 100644 index 81c40ab08a87d975b0e61e0d8497fe4dd3c11da4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25516 zcmY(KbBrh56Yj^hXUDd8Y}>YN+dHZYyKjCsH@THcx>HI2(I=mNQcv|c z?($;HK)^u%!ng_u@!tiQ;JE(7f&K>>pMoC;JvgR z-Pr(nw|aT9$>L^ruLDbyAJi;YUeT@$8nw|CgB^CS5oL60aun>7Iw6Q+$7luH{<%Le z6dwlqyl*iCc$<>V-E81PfW25DWN;?BUQvo#f~C-(G@pz%wud~+{HFGNdmbVx43X01 zhGMui!|aMSm^$r!;f{C!A5qSy_jX3($Ftu?N^%j3#*nIx;`^tkUzY#1cR(Z-AiKcG zm?av0eS`7XJi@vMK&fVHI;Cf_R{y)TXEBAw^Qh64?IOUZA?|Y!>C|7T^q@qp)!tkCQyJPCL^Lup*zhQN)c1xyT2#TvFcwY%3SGJec(^NN>1tR;8Kha>w&7;8 zXTm4{&&%v{`^}8}$S;}S5>-b;V3gIncQZ4GS#`3x zg9QGQ#QQwepoUUIHZ1+z;pZvud z7M#tR9Fbo^uB>cpQHO|1?~YO*NSghhy?ru3z|F?I1a3WH_qc&UzWr67K2}nBTj7ck zE@BSgKRkOfl{ioRu|A8Ls-a_Luy};`#(o>#DKj(;j>7cM!lgRB)V)HLLn|^)ux*Ey=fl?w7chihbIEnPA~Yr@ z6b@229<5xWhRp%5XbBO9-^KyROn?qbiTSBfcp4|$IYa4!uc*NFWLp-MI*neNAg#R2 zJFPlmH_f+E8PlCe8aFOkonv(VI(G}!H!k`3nfM&vb5wX5g@}n69Fkt-L~$MX=Vs^C z{zhPV z9{=F?M;HhS2>6@`2`cNZQC86D#lAEk6sRvj5r|0{$vc;#vA^Yzcg9fIcH(2v&1+@d z@FuhfgAPw`>MA2cZlLBKOyXOCiO{>DATJ3NCDE`+Xe*(>z?H=R5wzmi-ig@NLa~nD zTTMD19|35dbe>fQZP?C9&`KL5!0 zqlc^a_XSQT%V_T9T*wC~jsHf}=JBo~2zQ~#mh;!RPy?4{q}ph4oS{%$N8V| zHMX5Qb;vWaq){%ZB-GSKKtDt=A{8WFLz2_xUj27f;jF&fYdX5hDg<2qBC)I1ll8Fg1_hPfO##oPoVa#3QZ4R4!%KfCVxvy6Lc_+Oh zd{L3L6IpQSY?{yFV@u|!Y{Drr!;NfRRGpylTB}LhUi6)*b&cpZ1!URQCPIjFfFrX& z>NBm0U;u=cMD4mIN2@`F9Q8dZwn)ugFxX$aOzU=l-T?I7&kJ=g!21v`q_BmG`TY>B zzdA;^bowy%zR+DnQHS1j)2U$sRNgmZ7v?&{h6JZTy@%`5pkEZ3ld4N-O@!*2t9~O^ zL-{u%97QRujHEw61p7#yIaAWYdld7qDxEHz_-m7&j1Z%5TmI`(Bem&5RPnKV!h++ zBbG~(uBK~FTa){uWXeT2aC}bWS`8o(<^?!{tK5p8B}PdLSTL6?J(lNZYF2s2nDqrq2gI>R#m{CX5nd6QT{$fnnFMkHp~`L=UJ7 z!UOUac^9_NJwPAGANUy*m==f)s7})$E53rLnYe;L(kvYV*+hJiz93toL(Ii`w*yJj z``7+_REdoU2hFzfd87Y9Nly}dn+;TgTWi0u)!Wt~Z_!ayjk^564E;m{PLVZ^Qx$lc z{~LTnRgE@Gg;|Yi+v{DXgz%Hg8vB%fytlhAegnWcIWBj> z;X3=TwCmM3^ls0%{qhtmjqyucR_n^UXzw*c%tfyL+#XsgWsGRTy9;0A;r!BsXCkb| zBtp+22md|A6&M)O8+1I+S2D~}p4`-ix4SdMLJ#thWq(hu>RNoI{ql%}Gds}z8CXJ8r_RdIU7);c0T5Pwc~xpyV%aPHxwAiQmPL<(rj2GJ0bVk?UrhRFw=A_1;F&!!ipzy0Movbci0 zB1G4)zhBdE+;+O|CVJ@9nw8{M|(>IvhukAt~y|A$(sG2 zRu0z}o<4*Nv=KaDj8VyZOwF?k3V*CBX^@VE&=M#y)dgmO&9-F8yfj~BU>E>VYe67- ziZrfywA!3iMzaQ$a;kqskS8=xobMKE6l&I=$`t?pTC7m6RZKFY)52s`I|GLhBR9iX zHab}2#PUMM_TUN%l`~d~D*YZLO_oY!NE@&cg`DG+SsmPiM*c_` z?Uhi?xu<2&>-K!vQ`Qw$mewx4_?Fre(L0(qi){GAY(k(&^_yXSiV8oOb^?D9FH~r&nA#SUa;h*&Eh$? z5Gpc*6lq1#^dT*0+H~jbWfTOxarm|q{K6UhwE3S!lS8y*6;XJVO2taK?qN|`Lp@=* zXqidcDw|8(e{VKLauoBri;1+T$Hn-!!{tV_{fPvk0xpyYAU00PXtw!BQn_Txgq~KT z!DuS9+GfR8+(`ZTlG_1FGW=Ql+}`EqWUUkd)=7&ptqql|Fg^0%rzCyGVM@5*Cp|<`qhNT}$ot}I=Kp$(cJyq1U3h>!J1!o%Nepa`=rcy)Gv@9dC` z=u!b$q?NFMkhkvTYa!5ea+u2uBMbN0z|jIjFz${pQ!0x=;jwKd{N;F?UzY3C37W$4#*iK&^kfxfQVe*6w=Z?Prkk|qZr`yb+QOpMG-jdd7V zOx%zZnC=fqUC0EVAVKTWkon4K*Ym;`Sk^`$ScsI(CN=9!hBD!`O~dL= zfaZnDcEhI=W(9Wq*NqEf5PKK|-na=Yj#s-z~jwG=&~u}Ak2qq0t6 zs~Z?|?Wut4oFcsc;;7`R^{QLpL>mF9T#Yoj6Ywpu{%&q^b$NP(@7P;`!c?&t$1~bc zv`!yZ&=HoxLoboTCPBKKhjGDx8;nPX!rPhoY zk>f&wj?D>9raA}ue-%u*-r2#)@!|ds7Rt*0s|11^TShoE#sm(vWHP*Enq_Z2S|i$baI+H6%+SKw5Ta719mw2WrDUuNK>N&RNp^aa&%KUtU~YSXpX6 z3y*m?bRAHKu8$Z{_g2(2@Qqs@G^`#74<#f|JWu#PisX9X+yI5k$^TL0k}f!h0xK;6 ze)ES^RA|KzngE%zYW$;Iu&CBmUR0&bF-yXX2B=FT{|i1Q{UJFJ3EF6@-t!Zv*D<EKy?L;5Pmr2q|XDs%+u_i=Eu&)-0uouPF#uUeCW(>|4SAB|bNeUJavSK1L; zlG+kmlhYGuh+RIrT9oA%MR}}Fs2RBUL+E#!Y3IlKWM>?mWDffxqm;P!lpZ0@v5ZaA zFF+={&(Z0mcY)GQryF+B&g*$P?ZIRR5)fz91O@~|tIH))O26gZ5wn8B`rnkr*bTSS za;66~EdJ>fIsj$`e~Gn(n7Flg!(+)K0tUc-N`Fb`t-tlQQ5vnFs%jUQr>FhzvE5)I zNCyJE`S2<^ue9B$HLN|rgjcaGoQz86u;s3D88bgZ`6xiH!Tar+vQ_s5@sY$1RI7RN z1Oo+S>d?`NDkVg?)m;#e=Cnf*WyAoHa|H$d0=WrVoW1%3v$~o)2o9lvCUm;){8hDU zQ1$O%8&#C1U9CyfthzhzLe;t<2c-EQMt9M}-b>eG@K4`AA~r}gmd!RomYpfe)SvoC z3kKMMn^daqW}J4uYVkt(kBIRnV4wE8O+(uPtjhZS=t4(L^fz(C}SSO;9 ziWrlYVAVlpg3TyXg={4on*FL97FRjsip)WHZx^) z@jx=RGX||@eJZ>SUFcx;G7tOBh1;Yr-?*2N0vn3W!vr$hZ+tT)Qc-r!2fL0LVJX&M zsB8`%Lddd-#O?)cK~}AEs-Y2pu65d#y-Vgo1<+zkQc_ryvIhp*^vq zC4}1Al&NBy(dp98Iv=lf%f)+j2H2Nn3@~1KUqWgBAh(^yAR7HQI+=--Tqc7m7wL6B zixW1rgTQ%h^XjDkp?{RrhAv@gM%*{+6C9>h{;nbyPpHy%<0>U~D4{)ChgO>#zDFCb z30C&->iaYKatl&KebOH0J&SCSUYL)(Oj*Vm2@<$R`~YY`!aX# z>C*j?5ge-^{wWUbUI9)7v0F~suDMH>8?NN`L?*8#97z}#F5+hkB|!)s8vgwdn<+nZ zC=vqlB4gKKXrOt;>Uz`h_3nUD0EnVAd;c;Uiynn+ok+9eFLU!+DZn{U?^mMfVE8ZN z;l)hG{OM$>Co`WVgXJo<2aM*o>rPm|x1$37@3upNBV{P2P88V6j7<)$NqJ6tQk5x4 zJf#W55NKgBWa98bh#1N;NmR+jeHbd25xfcAyMV-6-jd4>a|=69(IH`ivJB?%Yi;MI z0j+OfklEW7hI#`b93TPLp04L}r*ork-bwhIrsbOTl07_B9wAHyQ9^+yM*K;>rR} zew@^oRQ0J8P*BMe0+k*#=>{8r8GifXCnKq2%&{$ojTXQqKA+Qh*!1u;ku=~Ayb)hH z;q8of?tCX;k5=Hbc|P!egQj6Oiy9eo23tT`7};Jr;Y9JUr>x{Yd%!;5KM(Yc^KZ3R zfa9w3^L!YkX)vQ4jz%TRCyX3PIhL>Ak9X5gsXh2o>mR*05-QKj{((IY6<=yUSh3=9#iRb`)sMi6FbLF;)k7NgssVxtsj3J{8E=emuiduql`LcfqN20i zmHH_0ya>-!$CZ(WYFQPn)b*lxEm}ucEpN)5(+5VoyN`uuk{5P33jogD{(gSS0j;}H z7>2|;Prhy3 z`MXy6gFvZ;&jbrAzyqZ!3kp2qdVVAMsZp1_dZ91;g6Lmen1TBAYvDO>0#O4=VFA8x0GIo1we5BGUJ4+jy}EGW7Uo5d5_JK* z=(LsdFT=wtyVWxrfSX5t@~Vx3@MhM!h^lQQBlg%5Ba%@#6kRCAC*_k2)-sxXPL`s( zkQwtgCBlwchlY&$6OT_o{%r+{*%5KYt!Oo^ZY>AUII5^xE^iUvF8*h?pHIG8a!)P% zjhxSvi}7OD+Orw;_;+38!l3VcrolPWRZG(3x2g^FO4I3Ia!7&g<}bOKWkTj<#;&Th z5(Vq>s-UU%@`lsxm!kRjaCf7^(8Mh)T39}7pHsVL6?sQpyh-N-SN^oBEpAMj&aoMl z0EyU0y%j-q39HhfkI=IGE{D1TLm<|?>lJX0QZ^p=Zrv?{DLafH*L9>D`*`j)GSsWMk z;gNfXxL-K!I6#IKxB_QzTkxEWwNw=w6yV&!OhB-BtS00ixTPg0cc6?M?GhaSy0#CC zuk&{o4+C>vHkp>Y4HUEn$jb5GYbkhivlLkYC&}RRMok4pglwZi+i7iY7ix@d$YNqI z=ybAI>X$wD1?P#8-*~K5&DS!N_gO_D($L4fsU$@j^ueN zRqQ=mIpY`sH9W#{%y6Mh>$3OclzIdrtO?h%j%VL|8N{Ag4BQzoxInaQxjeFDKpM#r zA<_E0t$^b>+^c%$;cl3mcy6uuo50sUgH!qqLin9fBvGvu>_lFkDm1O-vRXf}R(w{= z>Y_{*A5FpYuZpmpXC94e1rRv@`A<&@2ur z0M0pjzfepD!_ka~i!vFBrjn`7%zRP|m#cUlFpfU1JBj&?)o@`e5!)SqXXoCVp;p+{ zKq1pD=G6~n{(kDlP=99%Xt~o@0Yeht4L*6#eX7f{g|x*5=`r3{h}TFJT5ou8H62YP z`7sw<^~th-xYFnZ;Pb`#1H(**=t|nB!Vpm&U!gd9n?&$643dlK)z%e=ob3+e65Px7 z#oQl&*|nhI+BMHJ@n{6|@qD^74-^*qLNn++bANo*6WIO2d3}}f2JZRcr+e+(%=n>G zf%<8EfDI+Pg9{V=#8XDO-!vn=^rx#YMKUE*UhNIen}w}2Xc_urJ88xU^H`{(W-_XH zsy|&V6vHJOa4O3-N4`efQbuQIy}{Me11P$|GT;bx!6ng(WvUypxu?;7@pd~!_D?VX z2lf{Sk4&ai&X^`*0?fj^Q)fH!@2Ft zF)ej6h@!t#Y=%#`YP+V_G)AU&RCTb`Ok{i`bF3#QQ|P&pSGxmRy?~aRE?6{Y>|Sq3 z;Dq~%YT?f2DrGy>@H%2}*sO-rsZhma&R-;R;~;#Qb{kbsv9>>~*0VifQ+*hEaYQGm zp1gA-8S`hN(Qy#6+jgN9$|UMhipMt{Ch)er9?uu-`||S3kOuM$;~7mC=2$q6GB51f z&Qdq7+b;CNb@pc&)9d#JV)_KlMWw|bE0l-3T{sF>vfYL%<%qquu+_V-kqX$eU8k}x zJr_9jy)PuTQ7i_y#UEM-leW22-NC$%gAY8_!%w*WdicnfVRmH0IaebZ8jR)vYs1FL z#A1}q(-zmF5C--+)FVSU;kgPZJ+q1xf~!7&JS5tgIUKV01hdp%g)ZDdCF&8)lGj5) zXLJT(2gUCL0~ii^Jh*cV(H2?-@*?;5iqg32mexiPtD2y^Rk%p2Rn4g)b}!j8 zJI)Swstg-w>0{;PtPmc0?shb;-GhL*X3NfCLY3h@&0vC2%4rt0``ccVO@G~IlTWT$ z*6cZ}O@5{WV;aTU*ybTKS`n0%lbJ4fWd%I(#D_UVVXoS$GI?e7zYG)VpWi3nm!G=A z_Z&2>;wCs#5$E;CgJ+F!RI_Q{Zc2q)FttSDrHz1X*z~-X782zLTf#%uY~mlZa6bh) z5{8bnXWKNVwF0K;4_w|J z)SVSzv8z+AUVS>n(5ua}?fj|6JDOp>uU|nzAr)5dgFc|b& zpS3UVcQ$X4DV1uq&<#U1ud*rS$9jNHd0kgqSpuz`C*Ctp)<+`)Bby>)D`kzw5-rYr zg3Uji&D+r5nIgq+AFWoUksBVjD5rP>wVkSGF`ZZL90eO338pprwfWm|k!#oD-gtjB z)>)@AuUwaKLwLv{p#v6>l*~w;k#2%tNN)X9cNpkS^0cvMpd7#3z$e-5*JSIZY<*Xi z`)vX~6Q;j(uli*lf5K<@&cIHD_5Fbyz=3)*OM!J2Lhrkf^#muOYLkAbk%EvNS`0`f zz$z4Plroi&6eKBAE}GW)wXn~1GI~V&L>*z|bS0x2EUZ^L-ejw}D8?FSNis%TaFYQ} zTJ#Her;ul*r%aDtt|1%|9|s@joWHQ14fyNzlblemspQG}e z48@ZrTb}Ricx~#)DA(5c+pF4#o-|{qCEmyDx%Yi}l8#2Tz32F=#qaM+nP;O zb<0}p$P@ziRl^3p_Z8rUf7iv3SVS_BUOho*NGjz&AuqOKJzuO?+3hlu)oQYIodZwP zneGIAM!lV0Vgd(wZOJ3?S=VZtgyC9cEB54C_iA^#wL$1pO}TK0<^Bn+suOkdlT*ca zXh5<|h=aVVqIXd+}H9*RgKREu0ZmrkV&H;(O+k6GDxH6P0|uZk2?TD>}f}MLLxRAyDpc~$Zq2QXfVqed|^1Fd7moE-#tbS zP0lFe7v7a78Eo49zJuB~|NDB?s5248WJ)=2lfLr5HuyBjA!LKG@=@x2@9ITxkw#Kn77UYoX`m%Gv zaZO_GpV+;t4dg?($Ow^VBwy*eG&+RZg7Bhfm{8^tJF{AkQL2Yl8$e5 z$C{YdcDXo_#*5I$9kVy01Y?X8ALti)eA_GnJqhryEMKr~y;N7&&^9Wj(W&Zu_}8Y8 zAfZ$!I9&ue@)9RH%ppW*hjdWOi%!s;D5NcpP=xm9)ZH0Gq`0zfAM`w{zQ(B8NS{{_ z-d1n%qQZ@!RCeq}V)QQ*!(wP|dXkDGCJraW_*L2StfJP}$gH8{kNl9F8W6UAaJWA88!_!_71Lm*?}VR<&n&v3Fl*_eIOuFdhBwXYJ18JOp|hEJthF{*SBc}b*O+Y?LfLHbOn`eLLb zDl`^-y3);Q#^AI$+amYBw_jK%#cZNh1D40@$3Q7GG9CsxXR;;X=ed*T$^^X|hud0a^5nuvaRKQ#4u zJ4Yvt(91PsI%WgleAljB^f|aAKz{EPm|LzW4;tMql|Bi2(BZqty(4YEan)j|BhwfK5uI3otz44PM zmO@H<(6;c_RmfL571xRVv8H+|@7u_7f;PUTbif;EDKau4D9!xHo;b-Ybnoj;7o)S5 z3aMf*zgG5*~{~CUC^SZn6kdxl3pegc-z%$sJrVa_rIsa%<*I(q+%YY~_g|K7&W zXv^V(2e_YdFc&#l-^{BN9^0aEPatD>E~oFegE)%-9PdIPEX8ncJCGAxs$i;XU5#s? zbSM5~pnN`vY9-az5i7kZeCtK^Q;>hF9IPn^>4+Wi3X^4muQg|+y@OI7RYXT`Dy`0v zi_SEn6Jo$|B!YxD1{m4P)0QEhbaq#1qEjLWF057vOm}h=)+NNX6LCTC0PzbDO+j~U zIMrBc9j9Q+0U2d^g5dcTDMje_?;O=#m(T{3S=m%M!3IBLWn>#Tmsl(QKkTVCEp{!? z58t_$tqxahpJ(AS&2L!P$cWt9u~yC@w3SgTaJiM6 z(#i`ap+}RQpxXq8V9PSlk*G$tQ3`O2@P116sLingawtwIL;(0AYTf5iQ`X0Ctg$e4 z$f(U4hofTnZ`};>VR5avhwXInab2w8J*vq#s&WVzY8n1oSHC+auw9W;rQrLSAQ~mG z(Ed^!P=hl#AW%&c<>-TUR_%G~U0}9?yb^3$U%{a4ua)B1yvEJz6PuPUnFjaMEWE;= z#hw-<{fEq>RR_~!rxCMeg5XRi5c~n$2+?*zw3IY!=#5`vDlW8m`urs>^S#)) zfEU+zIc8N*9b8kty?<()(@b86us)m1IzmwIjCXT5RWpAIcSac!S_x~`O% zj~S(j?UcVqm+VJ$WG(f%dN-xqi5@%D*Jk#RNekXhG(gpfcr0vMCN-~S%*g1>RlG6w z^uH_CB6a3nc!1M3;`@!$R^WwL`2C0ZaUwI03Nc_Pt7_7}FF{5+^cuv=EKN<-=Wx9| z{NCB5YHy`ufWBi2A@kbxA^$ghFeA*%}IX& zKdoimjS?HmZ}3H$+yYo0rG`U z4WoO>A*ewO%HI5**=7A64~fls!t5|>6NYI9B0WrIC`aX&*sR zWrW>*S}FA5t_QO@EMAxU7Zt2-#*Xvg3ZBBY`n?tDVk|J^WnDl(5(2{$sOgiFIvc*a zy56TAn;0xXFk#P56iV}Rs-{t;OU%`O-Wi}~4f7hGS23*l5}ie%w-N#sG!p1FjMfj? zDm(?&Zp8wWS=mMH^RhHc%_qq+9BchJXu$eX@HGXkS4lCM^<>>6B@>EhboTYD$IHZV z9;I&w7shuplBV=r9QADi+AESdEz&SyPt5$p04sBwAAV@|@;k=7I}<*h&(b^y%(4ky zN)49Tjihw7VFB58C>`Ac>L0mc8{rBG{ z8S?zJZqRSlZ$!haMcF~QFBO}4C6+@_u(?{L`3DzXSF?32k2aH41d@|Z^1`;$Mfd8Yp^D@T9Xsvb_;!6X>vxjEk+$%?Rmc zspvY=>u^h^%C;V7Rc4f*-m3M&E9*UfQqHTuTBL|Pn7Av0y!b{9O@YNse8JT#sHsJJ z8_(jB+!ya(X?Dn0mrkLC9L^K|^eFMLZo#FM7mQ+W?GH8H9MG%aotBou?&L2SNx5nm zyY7;!JOS}sz)p97#AzTIP@QDB<(iEnS6*G!k_Jhj+#~rbRmo2H$a2oEZhR{4Lx?D- z-mvjOx&)l?#O9hTQ;cLuhrm8>zTGQ*<$dx?Rz?tCsJS(b2`PmBn|^aH0%~@p>Edas zARX6lf!Ks*wxn+S+&V4!zV}FyNMH=Z;5>zcqRU^yk5nuyX4O1hae{Kri!|9=2T(HTi44;6p6Qry+}AJ_Pnp`fu_;w!O1b*RHiDiO*CZ8$>k|*!Or;+ ziTqdarXSwQv3h@%Xo5!BLuC^5I@@1omn^cNqJp^+O4evFRs=jy`w2P2Woc>sqfUra zq%^o4x|@GxbHUIPr+qy!XATEz?Qd_CTK(R(UA|jbbl@bA^hZhb^Zp?ee%Oq5ugtA! z?g`a7Sy}CK4z8^esy1SJ5E0LD=t@i+FMz?ed>bg5gKc?vEgwLKLchRBATDp$K5Mde zRySLS?uKrG=k2?lG8?ATma+W`(WBbF%|G5cE=!m03@A)y|GrOQ7V-8h(y`yLnmvva z+a-8COEHpq`b@}|O6u>^K~#(w>A~nx%0f?uv{~u%*8`3fgdf77h7sy9*Y$vh7|?jK zqG>rX5z3-<+5A+xC+7~j0|S>L*34V7&4e}o#`}@sfW2~ggP!~qixn-`zy7LMSuGq0h@j~c0)~J&pYc4GsmJlL2^6LO|E89Wvq+q`hQ_mU+jgm4a z09XcA5tm;?g9_^j)1N3s?$bY6!0Hq){eZ zK4brpXKTm9Xvmy61Bmt8ga3*_pq76v+PnM2OK-QBVB+@x&LG#Ur-~QQQm8)WB31IZ zM*I6QofGO(_gko}%eshb&8R64ZD9WZjY0&0 zRkmcM1X|VYHBX)w7!zDHh?&odVipmo-RC+)d|;5o>Kx5RQOYq^c#EeR2&E}a=jFjd z2r#_@VTNg=UQdVX=4B$<+Y^g>1nA{ES zcDY{sgKQMT5YRf`MnSIkyCY0?%+ygdm8{~Bk|DdG#357J7-?755@g|GCCUyZ>9sQmPOd2l^Aeox-rd$vzJO&+9?QWU4H`U4o>m%xY#pQr z+#VU%LB&2UcJRc2uz6d8$2?h)DM!RrT34b{(c4+|oL_S-b#TgR0y1p49%gP-o(Feg z+=eMqr`7+1CW={&GCB99#A|^w7c%6S5>U z!iMNbE5fJ2QS0eDq@R2U9B5!8lGN&WfcxP8Lf6^$Y6_tm{c;#9Py>XMppj@(7v-7g z21YIMSd32QtYXW@m!V_VHeFJ0Y7zY$qegzwIh716z5FFx*nz%)muxGPU?lba&Z6G5 z#T3mzQ=A>@j(kvN63nfQ?H&Lx8lUs@+Fa;<=g8CYsb@Xj7Q^Ce-RpNRF~ZoeorR?{ zn^?qAFV2~%4M=|XOlrF5xylc-t6BKy*7lMuB)7QFA)53IF*9E|CG0CBuXPZ3(E9f6 zyLhtNQf&ty%d)kqE^=GSmu3`@#x)~55pS7^V6{0y+};Ksj*Vd=b^oeZ?=e&%r&#qU z$Ja-wrI4k zgRP1v9Oz0)3?<3%w>^H|D~{-h7=J0(j1^0`CDVT8#N@z;p$kZkD58w0-1Mi!-$lxr z%qG?!)QOv=Jaqgd0w*~GouQ_DNiY5&^2FkHy35c^T3g$HlO~cD9_E%290JGS17m#~ z0)@|#1%K+bKROdT=|&Dh*p$5WyX7n5IX8CMS3U+U5j??y2pPf|Qg~zm=|HkDe71jR z52euy7*&zYx{WnpCDTVdytC1NWOoHhop9z1c**36#TUrD)fNBbYKH(Jz<+BhB**zu z@0Vs5$i8PNr?{iFOLGk|0qT&+BQ?X_vgGH*FHj^3H?mshmAa>s$iH+jnE50#2WJ$e4T6KRhL|Im>ARx$j-WCrz0|Ewm<&#=lD_*( ziztjgv#5Nc7H0>Oc)o+PU@SjO>H2!#bZR7@dn#q$6U>j9T?^d1c?%HxtSFsP{7=u& zdl#_SIfUNxyDJHk4KS}<`-)G>Ji!D*35)IX_npp9vVy?*`{=(qW-9U1+III>0fe3J zXUfyQt^5Z(-Z0Z6#me{T8)r**$pju{Ln=B7=XHz@Ki8jhL)Nk7E>`8`XYVM&a)ULyXkLsoxmbjrnO zQjgX*n^0adb3*97wUboKugfTLHIKl4z~&-m@UuMhnXY#lz1>)~b$qu}kG5>->*sBh z%A#OY8kBhR>}Ox1-m^Tx3FvYk({2k1>GKLIx5yMTWu#(?HL7VFtZjY9bb?7RrHZ<` zY^yGHFUvFGag}nyrQkn=2YPJk3Lb&}~ zKoKHMiEQ4@UPsO&)^dQP0lMF(RMQejTou@wH@_oa^&iQ;?-mJ^*eYVUWp21vga?vx zm59}}SN7CeR;V}5b|1yP%^G5FXK6tYXYJi1`&d!~ax=ov&>!@^AMQHI)T{1w|KSAk z_YV~em4an#7}?B5T7(E?yYpTzM+-oVHYiT*&h<@wS*&V?8vg6uN$`jNyXDX72IhxO zYDlEhj!p(uF#Ynt4|!&UP>LS@^2N$2V$-5G@rb<;LT)Pjb@oYg&dp;yg|`UELXo>> z;nJ#Vi2cYE3)`BsIW6ZEFMWKZ2Q8Thy536*|L*NEUWJIx z0EXL!;HYS@5)&sdYB)I{Uh8$W7{V1h+o{<$cAEL)6J_dI&8NygD< z_S@#4@!uQq-z}zRJ+-7s1`i^+Xj-P7;S@tTF*z|PFeL={i9X`Ny@lz|t($Tk8m0DC z@{reSVKbW=y?c-Fa~WAJmA^S1rbZbm#X(x-#gs`LJoFzDWTsu3no`%-J1V_qVC1M{ z%-SdOAf+9gf>YJjET*qW)WT%MDfMO-bsu#7*7!r@B5FEwivuE`siTzkkV2o6mMR@u zs~yu!wL*}VCrk)U&Ft(&MIDp2D8w^!r!Xm*_7;MxV1eG~ce-VW$b)bgj)((UP}+Q`~B8eAl;N9OsESe=DDk9{c; z!_dHj373%I?60>g0q4g`H5E$08;~s;qQH%3R*`Vtv@emZ z&Tlzws6VKCsEkSi5QD+;=CiFq+;LQc`dWHtxdzpV?b*=JpVCRJlKp>yLL|q+A{V`G z=mLrGwsep~`cl-=ou;O{e7FGW`kDe=#O!G0BxxV+Zzsi<;13#chIn&J7ZzHI%3EDk zHu(ADz9CE_KM^N+*dY?*EzZZ%bPw3xOxMro7;5;S&I!$y`dRp6G+#UCh9P0@qCGvGtPJ3P^rS6=BI*R{BC z)j9J{nnhJ$E5i|?rL2`bM8Rq4GYNy4$xXndC{bCb24rNP=c;Lpl;MPkn-V%2G*r*b zWhtK78gA8|W?oQRG(%+UDuTu_OiU>+zM#!q5fY_^4oXL6m{o;FW{+DE#MQo>a+wzQVBu@mW^hUtBFIS^i8L$|MrDqmM|HT zk%*>`9YdGPBm~8t9+zG)qEeOCC?eIVS5Pen{&`vQrNOt6aaDbV4y}wHqEDIEvYXYO zNU7L&BJ6QaKGN;V3mUpRZ2GrAs$jV6ul3;Cr}63KWzDmKA|o=LoxT{IlcK? z@l~N!BEuStok4zLy&S9E*t4iEiModi{@FGI19Mm(VNYA3py1VI;K08)Hbxm}?6#>d zb+6^|8dV1*zI5%u@5Otn-~o1~(%m1+w{!_n7RfD4PH_24xn95tS%UrgbcZNsp1dR5 zqpl_KmmX$+S&`^sfnY_nWl*nd;JoE;;(-s1@K|D+oBtv)3RvT{F1nI130c%hNM%+KAO& zfyIMdJ`h`v=Lz+}z7C4c!_FSypFsZ{|E+=fhRh*wKlE&vcKLcH2|~VF;yKedgu>h@cR)!pTltj=^};Y2%VQ>GTRpR;J$SX*)~D*TZp`MxuNTx-e$rW`D-gZV5D{ z>6Q~1iINcgfOw(e{U)mgO7vbCE-Gc2iv5Hnr=vs25|D}?j&_Y3sHn(4K|<2i*XlWr^fS4>#Jsa$HGm*^TIAjvr+VgX{!CrkcIzF(0u!6YW`{w za=~z}L02kKqcqC51*Tv^5r|fXwH6t=eD-!`gYe6p zrq2*mUy1S>+YTloeL%B7A*r)3$v}8^cIzXHeXFzA5vp7^Ys4#*88Ze?7@w=dp+Da^ z>ce8fIE1T7t{SCDVMJXS0j55*x_$>}J#gU~NQ zK?zWNOsd73vRy=XVz3k_LSjjv6aj^jP~*r5R>$c2e}ssyBQkgj3&g7r&Gv+E zseC>!Ssq$~fCh0rNn+NZFg63@rZDDhQL5V~1&S(;11aNt)Z0s_(pSWm+NekTBoHMb zy~RXDE-%sJcCIMnZ^^zzNv%{RaOrJc3J$wK(}iXPRIP15Sc#w)^{I%f`6a;VGVupQ zZJqCPKwqZTEVtJFIF1dPUazEWc|8}|dG>n+$^&4TPgK~i%igIi+dqAMLL$V8hKMza zu!s{zs6P_YHC+AIxB`5HBO^{GBO`I7hYg&cDi>~wGFUhMPXHq$+}uSeq`xU79-9+_ zyUvE{Iokp1ail&Ht^e<7<=hYQF^O^f5Iq5=r<2E2V-rEzVywG1b`ZI zU)^dFINbWLSZd_O7FpIL5sK+$8QgZVFc)6&p7`-tnYL+E1sp6p3)}@1{N2);bOLyfB^!oEk+7mx*8gQQFoP z>d?m0+?<3bVweMq8zVUG)Gr$Jy0P<@c@2V)i#N>2`>s?dWzfUdD|!wBn$j_2wY$j7 zMq+3N9@5qtqV=CLEkmdcLC9=Qh?Y`9u~|fSr*;-o;`e<^*)bN@j`0oMDf-D|YL~Ty zLx&C@I(hgF4yz6utXLtuwmLmqBY{j-`_AF-Ce#`^RNgu25^a!6)Q4tsX(n zr@D%7K!lvewId*0*F|F9c8k9$wid-C6M^qa;)NpiVtk8}%eFO6L=QckJz{KB3&ToL z`QhkTac!k!Qqp&=w6)2S-`-JKb2js&!ekT*3>vTu_Ov$C6`9J4)I^f%g=mJlR8%y# zt&Vk=o~y|-3nls4W+Icw5b~&G9-q$3&V~UNGsDP=+9E(R^{eYAt9f&-#G9CQk~OP4 zr?>s7!(CQv6$$!Y#wN|+u7~E?OV%|^0(QTMSq)4b{<*ACdLp4spAQ#`HEzQmaV#N^ z#f7snxzch|4m*dE#vru#nKhT2Atzln4k(hAZeC&l2sL=DVT*v%Drt05wl?R5-lZs zYZlHo&64uDC)*XCPbJJMTc)LKtSKP)CS&uHQk{ErwvG`=5opqTPIN?8-zUze(3iiP zRlz{zEXhYML1U71RjG!{nckH=YHqFso}1S+&WlS`brFwZ_c}++(v`_&KW?6w(6V$E!=S3k3|pzPmL6w)slM9bJ-EYgf_79w1uh00 zu875ZH9=0#qU0Pn&7i)eqV}SIC)+w*Ox9gROVES}K`V`3{6wjN_zapz0EONq)Ao?B z$s%iWPARe2;&bMpy;!nBN$GuRI8D+-Oi}?!< zaf9}Y9u@B{Z{g5QG!s|R;yxX8Pd&v5f}0)V)JY5+Dj_<=L)DCN^nBfI?pSY8bZB?* zgJg&ovQg)Uq!k?y7eMEUa(o+Mn*(;R2rRx{?)Cx%4C;4O?_LIQw+-lA=~YEri+k8u zD~%38HT-FCIY{8QhD^mnz2<^+b`H73(B59h0xuS+grJOA>A_4gyn7u5tlc zRWw(D=5yZkZWn`Jz{H2 z8rge~KaX4G71|$tg^ADVEs{7LS63ma=&cY}7`etcv8-3nyS5iB1u&0IU^`&PR>IbU zn(TCU1Om7m4-rRqv@6cs5a<;rC(v0|agGL0csjc>4dFY|>GireTXlrRo*)Gw<)aqj zW3lJM5aXW#pRrL&&ydm~BouhaX^!?@zuwfu5Rtg`C7a12epAL~$h&=zKnJ!+i^J zT>QRuQ`D(!IDLvX1)@FJ@(Etkf?ZhV3sqkCuNIlv_BY`_kFZ*}-9h+dQ8)4t!wZ_v zjCl9b$zK9(CaS&hfrW9u}tbUWd@dJiqQ?AbIiPd<*)JUt8Nsb7LcWk|I zYeUY4?Ag$QWq!>dKIByb?O7HFuhZuDU%V~*V!`09cLQGr4ve%kb1aCT0j}Wue#MI; zP+NPO>tV_%R@M_LuTmRfC+FeYN)B9J;oEdG_xN8(;lp4S{Ns9=F-+ggBC8K(oO>r zB)yXCL8X>khCiy}$oUv+$KnPdo3j5XsWfqjlv~cDo)wc*C~m9<|Ay8Tc-NPBsfkx` zZH=z9+8(?1%jcC>|wul32&6d8FGHR`?b`4 z%f{&IA$`y6^1Kj5GpVC99=<^;b*v{PqfTaWgA9{XgFP_k7LFU%lG0?{r@tpG)E-uX z4RdmHEvNcP8H;;T!>Os0)PbYIk7BC0`!e2b%pZgx^tlzt#xA`bO5Kpfyy=vj$>5Dx zL_3cTF*y5)iKs5d%l!|3OA%!dBa8?lZkp(n3P$T-q|Kd3Vs?}e_K3KF13&oVX!Z%xxd+^XYOF^e=Co}jtHz?P&ZwWG zhw?IQnDM>*ql6+mpGL7j#i%D)nYp7N@{;?S(|E`|cj@2z6#Vq5{Hb(~>VFSTGI{Y# z@;6aP@Od#&nSI)f$17|sN?J>0)Oy9^P>n2E9F=8bDwU}zd#e)`=uLRO|KQ4=exuqA zd-CwMQIpxdE%q`?ICN@$TLRJL)S+t{J3xNY42ug3%Pc|UujJjF1jAph_9g58A^gpV z;27sSb$Q)SsqjqFY;O_apul_rw7a06Xa83b=GFhZhMW%sPd94YvIB2YC~6Wv1+swE ze`QQ&44BMm&&BEF_fZSli=84BHhU2iy@o~cizpJ?1=fh&5~;q^xVDRrY3osl4PY@q>lU3V<_Cb$itl$4 z*dyYiI6@piu&C$qY2a&=mBX`fK-dJsfufYN>`Qy^_4Ss_btoN-35Fn2Qq5JBS(Ln< zD!#0m7Xcc+EM~{BodoJk3+)x2?7^Zn2$Tkx9`+(jx}#qalazGY_Gfv1N`&b2-&)W#F(LM2_uC zT#kb>psX6v=1T!@Hm_aq16&Rtxi@yj&}O)Dd9|7uqR>~Z+BQ3Qw zH7lG(dG?63KdJ$p$J5BhPL4pBmn%rz^frGYL1Z_Lfompcf4nrsei11)Ju&ovK* zi{j?@22KO@Xon`zY@6TJF>4bl32d24n!;mvJ_=o>2f|~7`~JKcu;}axOsASI#stq$ z5!2OC?Pr%SQD}ud(yDc3k%g&rDrROdhDuLETl!s<0v;0PhXPsgJNx2A_L9E7@x=?i zUOb;yr`P5rv3U0Ai^{ULxOjC*iC9)_O-vf$jQG!OjrG{)x`d6#Kq*w#bu`X#`a@AY>p1tXdk?OkH z6gH1Q7Vw9Zle0k)HG%rGIoNjskE;%be~5Ji%)6)P3qwaUzR&22r95ih+HMRSppYq) z{-1vtVU7U%;z1gbMjU+i<8fvq_{3Ti){qzd1Q;XRe_u>~{zn=3`RA+5tAP1G+cDeh z-vYPaenN@e^ytaMXE|2;1K{==*d0a?_QjJ&#!?%Y{tP3fHsh0rPam4?w$pZ-889b~ zFx8;Abdj}G!vxxu%p-?c>eABwQU{ngy7ll8rp9hxWG?~lS2K?sVQCy?17!|C`&6-V zRapg38>O#YSZdB`aWp*aowstZ&b53cTxGCVs5NC(CY{}xt#MX>!ISQDy>ffB)|Rf=w>aDIZqw+{p%0rz=SRy1s)XFnOO3y;NXfXB zarkbAM^dtzH@X$*!&6S575sQxJ{7s{KKX5^XF6dzt_NgSf8u-s_b-1d-L&M#IcNDR z{#pX4Xr;}T&CKJZ@bUaS4l~sd=DrO!Vv6R-vW)Ag0bIX3A7}#~}S_37fRvVg? z`=#t>iUBx-6kHi>n{6!xzcz3q$Bk956Xl zxqJoZfNI769CDU|pBn*!!@`gI#UBsABg+-A&nYE{o0EhiD+q%<^hf+vQS9->^eKtC zSOp^hYvi(A5-@$`%W<`f(ZUP`vDO<_@pbD@H}QPy&e=JDl?J_f6+O$q?ZIcyEL?Ez z+=7K?&IZS`fB6wGu~ih!57-ym;lKOD(yq=uC))yI1NKJowGDk85{Y*rBT?erF6{%q zkv85w9~o1wrh}4Gk4~ljI{9ca<=0b>Po+Ffe)z|;&g)M8T9s7NmW5C8v%Ycp2dqdx z@{QZSW#LzhQL0l)q>Q*|{GauuyuwzZM)A8*$dmXJ;M|M*Bn3(W4d(OY^SV;d9mz~2 z`~p)MX7bMGuOm+&QKzG7WiId2&km1;qfZ&!-uU93XKoD5*-?inS7H<`N<-V4 zk)?gBHn&zUnCI3U$Tv~fA2_^v;PhqS%0NxwEM0U~hM~m$@-+Ju*V{_2^4VX8+UmAt z4vvM|;`QwwlXED?xyQr*rxaiib#<;B@$(Xc4Heb0;)h9Dj@OKHYh|~j$FiGDx@5CrEjV-$$YdBvJBcl2y0M|WiW*tf`XwK zfCm~F4XH9-7?=`{{?X0WKQOXP%sX@nq6eU+?;Rh`!s?u7aDQl|e?&Z`O>#Mt!T!EU z2&`29D9o#h%n!g(;qGGkua9JU03NW=ExX~}(=nWJ3ZwCF=vk!&qVBo67G#v{TK~28 z)^*qDx~slDiFPfk9R8YFw*1R81uUNI6yJTx3I0_+pg#Q9y%Up2p{CCrtI9x97eh66 zxXviaQ8|u+-_a_N)V6-{(g3$3`cr>?XGT|6L@`PBSqH1_gBT~?KoN`Om@v8KwSlhD zb=o63R8pq4E($lbH9#_HSQs;63-FyH?3WIw|N72cwHmF=0H(kQ=xgXo%Rp4lkg|X; zR2B`X3Lqi+ZDXb&HH{!>BnZ<|fpu1PdUrMfcsYM}<7W|L<8u*sY%=9()RU7Gi{)g1 zLoMQR4Q+F@+l^s=%@dq}{J!t16`o`PhfJEK-&AV>qBjZ$Q|R+GkHx zW$dh~EQwV}&1*%O#`QY11&>5Y7n5mp&B0P_y!&j!+9w;XSr73l;kw^El%q36;gt^ymfJY!4SmW*=Xd8E}3VRI{N_5s|9{VDJBXS7#$<|supkd7~fiW{8!LL}@0hNpwFDohFQn=B=NpTWl_1Hu1T=xtiab ze*n((RyLL04RR)RtEuWaK<6%al(~@?GU)pe29@fw&0fGxMVKQAZ>~u6{9unPk~k&o zvwn6Vsbz)&AVi!b4)BiUo#X*T8c)5I2hu076O#A|;7yW;O(Dbn8`Yq$+Rrn!fzYM( ztb@FCm>+-%&f>RE*-d~_XyPU@ZR24$eng%+PWV-dV{&_q5@h_4IONaYiI*t^~(Db^g(1lB<7WLo1IbA zf|?yMb~NQpE-~hcqpu;eX_B?!cY672vOI(s)7n7Y&b^G0C3Zd_=@-l#t=#1GG78n? zSgC}_)EFJzO1#inV`sh}w%g!Xv{S)9H1e!iZ4h?MIX>3~5RViVGi{JG@DpIq>5CXcuCmDaD^AUoTIE(kIclZC?ON2dG%)L~ZW5B^4=mCA;2mI&sn4Y!A zW-^k}1QK~dsuE4e71HJNo~LKeIgT;{${y!e1?C~#B|-DSAE8*)7MR3FC#n7C^O#;We8We zz;@V01`Zfy_BHig9Ft`G7-QWzEjO7Mgwe&cDivk-)ER+TU`ekH>F_NtC2(`5JCwsm zFfcV**PIxw_o+(7v1@KiYBeNRaq`WH7L;@~(cURrQ>y_(YlMiJUEH=J+Ve18ZCUTN zr*r~pl|*aDP5T03ZMa0Zj3& zj~*QfVZ?g&jR=wZNI;?+NhlSLWQbZJH6-NY{ximk|sKPZ1n5LkIz~ zEQE+sLY$-sAtHjHZnQO6(S(ShI0GWinzbVo?DhbWuy?T!lf>fn%YgtUz1JoLf&9Az zVi9v8zP<(7TNJcJ&JehWLoAxNV9n_nUu0>C1|=XMg`jc?#^fq3z1A7I*<_D=xI}E7 zQuZKOGA0l2@LwY^3s~)e`C)LH0;Wec5{w*4lLj*;Z!qB!Qq?{X`;7o0B+XO6=kH{I zF3pG!RX~eQ8vz#0nt%DWZfT&ZdbW%pN3$ZrX+sQ`l8^z5`V@vlGS9p_BQlZ^>N3lF}iA}l^aaDSRS&ccZ1 zM?4Lu7{hxzz~Fvx|9pWrhBb z{_Ata;9*oKVYPIi5_UlG&|s>{UbcPKbTC~^VuPab71{t%D&NRa!a|j`8RI9%5{7Ow z{>Nf~*y!HOr~n|BhjeY`LmwQNu$@wv5MYFm84uyp&o62zQCWeiy@=tcxJEo2M=K$v zRFLNm<*4Dujv~nlRY|J@E3`m)EHDI}fY5rY8Lt&BqhjLNXC!EwBOBa4U}1vWSq?vx zq|H$od@N$Y38Y>~>fE|w%>o9K;3Mrf43HozNkm_LZD44aK8ies!-C{%M`AXsH(k=L zl+T>J)u2@t^hYc?$d#JGsm*#v%QJn9bGopqX1iP&$*xn$$wv=qLp|1Oq6mFFCwz_~S4p8areSDH&J+!cOm1kKX&BvL z+hH~en*v+MwCb8cS`;pJ^J$>hR-9i08W-oe+&Gu!$GNmI9rLzrOw*5@V(gEi)UEO2 zET0zcq_b7wUO_$$`xARQME6tH?rDR1F5pLNxZIDtli39rUY+&&!tIllt$ElMje6|3 zh!(mI4r)pDc4tP^;Vr|$rbi6IBBi;U_9E;E75YSbkm(3D@r0+B=0O^&R~f?q`vdbH zBQ)t+iYGuhF5+Z+iBm52*m}gokVP{c_M6+KnTK3*$)ywuV%-+gMQv#lcKkLC*=J=Z z9_(zE?6`B5WQ%FoJFAjUITzpt#_vRKq)`fTzrO0l13{ZNx-pvW!l!ea9y&@R3>@hb z2jIw6mr-fOjdz_&WQ5fDVr|InM@nPqOMT%E+7KWJ&`5v_0fK)Ib55N+Sp? zzF*e}?cqAn#biJ;;m+Z!M$fGGptybBcpbs6tM~kV6&kcvLucGbdII%d-Uf5vkeo9e zRAKwgyAylK6i=`{ zH6pF5 sqP8SqnK2V^N)*ViYwk+vl(A6adBxtti&n8K3SR-+il0-{okyb}1;i0a4FCWD diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss index 1d1be4b0df..2f47e1d3e3 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.styles.scss @@ -83,3 +83,40 @@ } } } + +.lightMode { + .team-member-container { + .team-member-role-select { + .ant-select-selector { + border: 1px solid var(--bg-vanilla-300); + } + } + + .team-member-email-input { + background-color: var(--bg-vanilla-100); + + .ant-input, + .ant-input-group-addon { + background-color: var(--bg-vanilla-100) !important; + } + } + } + + .questions-form-container { + .invite-users-error-message-container, + .invite-users-success-message-container { + .success-message { + color: var(--bg-success-500, #00b37e); + } + } + + .partially-sent-invites-container { + border: 1px solid var(--bg-vanilla-300); + background-color: var(--bg-vanilla-100); + + .partially-sent-invites-message { + color: var(--bg-warning-500, #fbbd23); + } + } + } +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss index 984b89828b..0b37c654be 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.styles.scss @@ -20,8 +20,8 @@ } .header-container .logo-text { - font-family: 'Satoshi', sans-serif; - color: #fff; + font-family: 'Work Sans', sans-serif; + color: var(--bg-vanilla-100); font-size: 15.4px; font-style: normal; font-weight: 500; @@ -38,8 +38,8 @@ gap: 6px; flex-shrink: 0; border-radius: 2px; - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - background: var(--Ink-300, #16181d); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); box-shadow: none; } @@ -50,10 +50,16 @@ } .header-container .get-help-text { - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + color: var(--bg-vanilla-400); font-size: 12px; font-style: normal; font-weight: 400; line-height: 10px; letter-spacing: 0.12px; } + +.lightMode { + .header-container .logo-text { + color: var(--bg-slate-300); + } +} diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx index b8982baeb1..9d4df97676 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingHeader/OnboardingHeader.tsx @@ -4,7 +4,7 @@ export function OnboardingHeader(): JSX.Element { return (
- SigNoz + SigNoz SigNoz
diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index 5737379d40..22cf4c6b2a 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -24,7 +24,7 @@ overflow-y: auto; .questions-container { - color: var(--Vanilla-100, #fff); + color: var(--bg-vanilla-100, #fff); font-family: Inter; font-size: 24px; font-style: normal; @@ -37,14 +37,14 @@ } .title { - color: var(--Vanilla-100, #fff) !important; + color: var(--bg-vanilla-100) !important; font-size: 24px !important; line-height: 32px !important; margin-bottom: 8px !important; } .sub-title { - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)) !important; + color: var(--bg-vanilla-400) !important; font-size: 14px !important; font-style: normal; font-weight: 400 !important; @@ -68,15 +68,15 @@ align-items: center; gap: 24px; border-radius: 4px; - border: 1px solid var(--Greyscale-Slate-500, #161922); - background: var(--Ink-400, #121317); + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-400); .ant-form-item { margin-bottom: 0px !important; .ant-form-item-label { label { - color: var(--Vanilla-100, #fff) !important; + color: var(--bg-vanilla-100) !important; font-size: 13px !important; font-weight: 500; line-height: 20px; @@ -123,8 +123,8 @@ height: 32px; background: var(--Ink-300, #16181d); - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + border: 1px solid var(--bg-slate-400); + color: var(--bg-vanilla-400); } .ant-input-group-addon { @@ -134,13 +134,13 @@ background: var(--Ink-300, #16181d); border: 1px solid var(--Greyscale-Slate-400, #1d212d); border-left: 0px; - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + color: var(--bg-vanilla-400); } } } .question-label { - color: var(--Vanilla-100, #fff); + color: var(--bg-vanilla-100); font-size: 13px; font-style: normal; font-weight: 500; @@ -148,7 +148,7 @@ } .question-sub-label { - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + color: var(--bg-vanilla-400); font-size: 11px; font-style: normal; font-weight: 400; @@ -192,7 +192,7 @@ } .question { - color: var(--Vanilla-100, #fff); + color: var(--bg-vanilla-100); font-size: 14px; font-style: normal; font-weight: 500; @@ -209,9 +209,9 @@ border-radius: 2px; font-size: 14px; height: 40px; - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - background: var(--Ink-300, #16181d); - color: #fff; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + color: var(--bg-vanilla-100); &:focus-visible { outline: none; @@ -233,10 +233,10 @@ .grid-button, .tool-button { border-radius: 4px; - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - background: var(--Ink-300, #16181d); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); padding: 12px; - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + color: var(--bg-vanilla-400); font-size: 14px; font-style: normal; text-align: left; @@ -271,9 +271,9 @@ align-items: center; justify-content: space-between; border-radius: 2px; - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - background: var(--Ink-300, #16181d); - color: var(--Vanilla-400, var(--Greyscale-Vanilla-400, #c0c1c3)); + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + color: var(--bg-vanilla-400); box-shadow: none; font-size: 14px; font-style: normal; @@ -310,10 +310,10 @@ align-items: center; justify-content: center; - border: 1px solid #1d212d; + border: 1px solid var(--bg-slate-400); border-top-left-radius: 0px; border-bottom-left-radius: 0px; - background-color: #121317; + background-color: var(--bg-ink-300); border-left: 0px; border-top-left-radius: 0px; @@ -336,9 +336,9 @@ .input-field { flex: 0; padding: 12px; - border: 1px solid var(--Greyscale-Slate-400, #1d212d); - background: var(--Ink-300, #16181d); - color: #fff; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); + color: var(--bg-vanilla-100); border-radius: 4px; font-size: 14px; min-width: 258px; @@ -366,7 +366,7 @@ .arrow { font-size: 18px; - color: #fff; + color: var(--bg-vanilla-100); } } @@ -395,3 +395,194 @@ align-items: center; margin: 0 auto; } + +.lightMode { + .onboarding-questionaire-container { + .onboarding-questionaire-content { + .questions-container { + color: var(--bg-slate-300); + } + + .title { + color: var(--bg-slate-300) !important; + } + + .sub-title { + color: var(--bg-slate-400) !important; + } + + .questions-form { + width: 100%; + display: flex; + min-height: 420px; + padding: 20px 24px 24px 24px; + flex-direction: column; + align-items: center; + gap: 24px; + border-radius: 4px; + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + + .ant-form-item { + margin-bottom: 0px !important; + + .ant-form-item-label { + label { + color: var(--bg-slate-300) !important; + font-size: 13px; + font-weight: 500; + line-height: 20px; + } + } + } + + &.invite-team-members-form { + .invite-team-members-container { + max-height: 260px; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 0.1rem; + } + &::-webkit-scrollbar-corner { + background: transparent; + } + &::-webkit-scrollbar-thumb { + background: rgb(136, 136, 136); + border-radius: 0.625rem; + } + &::-webkit-scrollbar-track { + background: transparent; + } + } + } + } + + .invite-team-members-container { + .ant-input-group { + .ant-input { + background: var(--bg-vanilla-100); + border: 1px solid var(--bg-vanilla-300); + color: var(--bg-slate-300); + } + + .ant-input-group-addon { + font-size: 11px; + height: 32px; + min-width: 80px; + background: var(--bg-vanilla-100); + border: 1px solid var(--bg-vanilla-300); + border-left: 0px; + color: var(--bg-slate-300); + } + } + } + + .question-label { + color: var(--bg-slate-300); + } + + .question-sub-label { + color: var(--bg-slate-400); + } + + .question { + color: var(--bg-slate-300); + } + + input[type='text'] { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .radio-button, + .grid-button, + .tool-button { + border-radius: 4px; + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + padding: 12px; + color: var(--bg-slate-300); + font-size: 14px; + font-style: normal; + text-align: left; + font-weight: 400; + transition: background-color 0.3s ease; + min-width: 258px; + cursor: pointer; + box-sizing: border-box; + } + + .radio-button.active, + .grid-button.active, + .tool-button.active, + .radio-button:hover, + .grid-button:hover, + .tool-button:hover { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + + .onboarding-questionaire-button, + .add-another-member-button, + .remove-team-member-button { + display: flex; + align-items: center; + justify-content: space-between; + border-radius: 2px; + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + color: var(--bg-ink-300); + box-shadow: none; + font-size: 14px; + font-style: normal; + text-align: left; + font-weight: 400; + transition: background-color 0.3s ease; + cursor: pointer; + height: 40px; + box-sizing: border-box; + + &:hover { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + + &:focus-visible { + outline: none; + } + + &.active { + border: 1px solid rgba(78, 116, 248, 0.4); + background: rgba(78, 116, 248, 0.2); + } + } + + .remove-team-member-button { + display: flex; + align-items: center; + justify-content: center; + + border: 1px solid var(--bg-vanilla-300); + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + background-color: var(--bg-vanilla-100); + + border-left: 0px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + } + + .input-field { + border: 1px solid var(--bg-vanilla-300); + background: var(--bg-vanilla-100); + color: var(--text-ink-300); + } + + .arrow { + color: var(--bg-slate-300); + } + } + } +} From 2fe75e74cdce2ede111d4809e13fdf73c559e47f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Oct 2024 10:27:08 +0000 Subject: [PATCH 19/52] chore(deps): bump uplot from 1.6.26 to 1.6.31 in /frontend Bumps [uplot](https://github.com/leeoniya/uPlot) from 1.6.26 to 1.6.31. - [Release notes](https://github.com/leeoniya/uPlot/releases) - [Commits](https://github.com/leeoniya/uPlot/compare/1.6.26...1.6.31) --- updated-dependencies: - dependency-name: uplot dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index ad43546550..c9fd8a0cf4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -123,7 +123,7 @@ "ts-node": "^10.2.1", "tsconfig-paths-webpack-plugin": "^3.5.1", "typescript": "^4.0.5", - "uplot": "1.6.26", + "uplot": "1.6.31", "uuid": "^8.3.2", "web-vitals": "^0.2.4", "webpack": "5.88.2", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 4af84c5df0..8d0ed82a89 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -16588,10 +16588,10 @@ uplot@1.6.24: resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.24.tgz#dfa213fa7da92763261920ea972ed1a5f9f6af12" integrity sha512-WpH2BsrFrqxkMu+4XBvc0eCDsRBhzoq9crttYeSI0bfxpzR5YoSVzZXOKFVWcVC7sp/aDXrdDPbDZGCtck2PVg== -uplot@1.6.26: - version "1.6.26" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.26.tgz#a6012fd141ad4a71741c75af0c71283d0ade45a7" - integrity sha512-qN0mveL6UsP40TnHzHAJkUQvpfA3y8zSLXtXKVlJo/sLfj2+vjan/Z3g81MCZjy/hEDUFNtnLftPmETDA4s7Rg== +uplot@1.6.31: + version "1.6.31" + resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.31.tgz#092a4b586590e9794b679e1df885a15584b03698" + integrity sha512-sQZqSwVCbJGnFB4IQjQYopzj5CoTZJ4Br1fG/xdONimqgHmsacvCjNesdGDypNKFbrhLGIeshYhy89FxPF+H+w== upper-case-first@^2.0.2: version "2.0.2" From 860145fb1db3fe0a96034bc50df309cf175318f0 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 30 Oct 2024 20:30:01 +0530 Subject: [PATCH 20/52] fix: handle redirect in onboarding (#6324) --- frontend/src/AppRoutes/index.tsx | 14 ++++++++++++- .../OnboardingQuestionaire/index.tsx | 20 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index cf17fd4db3..2d0f4231cc 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -37,6 +37,7 @@ import { UPDATE_ORG_PREFERENCES, } from 'types/actions/app'; import AppReducer, { User } from 'types/reducer/app'; +import { USER_ROLES } from 'types/roles'; import { extractDomain, isCloudUser, isEECloudUser } from 'utils/app'; import PrivateRoute from './Private'; @@ -74,7 +75,7 @@ function App(): JSX.Element { const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ queryFn: () => getAllOrgPreferences(), queryKey: ['getOrgPreferences'], - enabled: isLoggedInState, + enabled: isLoggedInState && role === USER_ROLES.ADMIN, }); useEffect(() => { @@ -95,6 +96,17 @@ function App(): JSX.Element { } }, [orgPreferences, dispatch, isLoadingOrgPreferences]); + useEffect(() => { + if (isLoggedInState && role !== USER_ROLES.ADMIN) { + dispatch({ + type: UPDATE_IS_FETCHING_ORG_PREFERENCES, + payload: { + isFetchingOrgPreferences: false, + }, + }); + } + }, [isLoggedInState, role, dispatch]); + const featureResponse = useGetFeatureFlag((allFlags) => { dispatch({ type: UPDATE_FEATURE_FLAG_RESPONSE, diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 1917cd7a55..4cb3ecaa80 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -25,6 +25,7 @@ import { UPDATE_ORG_PREFERENCES, } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; +import { USER_ROLES } from 'types/roles'; import { AboutSigNozQuestions, @@ -69,7 +70,10 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { function OnboardingQuestionaire(): JSX.Element { const { notifications } = useNotifications(); - const { org } = useSelector((state) => state.app); + const { org, role, isLoggedIn: isLoggedInState } = useSelector< + AppState, + AppReducer + >((state) => state.app); const [currentStep, setCurrentStep] = useState(1); const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); const [signozDetails, setSignozDetails] = useState( @@ -103,12 +107,13 @@ function OnboardingQuestionaire(): JSX.Element { } = useQuery({ queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }), queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'], + enabled: role === USER_ROLES.ADMIN, }); const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ queryFn: () => getAllOrgPreferences(), queryKey: ['getOrgPreferences'], - enabled: isOnboardingComplete, + enabled: isOnboardingComplete && role === USER_ROLES.ADMIN, }); useEffect(() => { @@ -129,6 +134,17 @@ function OnboardingQuestionaire(): JSX.Element { } }, [orgPreferences, dispatch, isLoadingOrgPreferences]); + useEffect(() => { + if (isLoggedInState && role !== USER_ROLES.ADMIN) { + dispatch({ + type: UPDATE_IS_FETCHING_ORG_PREFERENCES, + payload: { + isFetchingOrgPreferences: false, + }, + }); + } + }, [isLoggedInState, role, dispatch]); + useEffect(() => { if ( !isLoadingOnboardingPreference && From fbe75cd057b9910cddc270f764758ab4b44a55a5 Mon Sep 17 00:00:00 2001 From: Shivanshu Raj Shrivastava Date: Wed, 30 Oct 2024 23:13:56 +0530 Subject: [PATCH 21/52] fix: use query builder for metrics onboarding API (#6327) --- pkg/query-service/app/http_handler.go | 95 ++++++++++--------- .../integrations/messagingQueues/kafka/sql.go | 15 --- .../messagingQueues/kafka/translator.go | 56 ++++++++++- 3 files changed, 103 insertions(+), 63 deletions(-) diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 9ce110d545..968aca0030 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -2833,7 +2833,7 @@ func (aH *APIHandler) onboardKafka( return } - chq, err := mq.BuildClickHouseQuery(messagingQueue, mq.KafkaQueue, "onboard_kafka") + queryRangeParams, err := mq.BuildBuilderQueriesKafkaOnboarding(messagingQueue) if err != nil { zap.L().Error(err.Error()) @@ -2841,66 +2841,69 @@ func (aH *APIHandler) onboardKafka( return } - result, err := aH.reader.GetListResultV3(r.Context(), chq.Query) - + results, errQueriesByName, err := aH.querierV2.QueryRange(r.Context(), queryRangeParams) if err != nil { apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err} - RespondError(w, apiErrObj, err) + RespondError(w, apiErrObj, errQueriesByName) return } var entries []mq.OnboardingResponse - for _, result := range result { - for key, value := range result.Data { - var message, attribute, status string + var fetchLatencyState, consumerLagState bool - intValue := int(*value.(*uint8)) - - if key == "entries" { - attribute = "telemetry ingestion" - if intValue != 0 { - entries = nil - entry := mq.OnboardingResponse{ - Attribute: attribute, - Message: "No data available in the given time range", - Status: "0", + for _, result := range results { + for _, series := range result.Series { + for _, point := range series.Points { + pointValue := point.Value + if pointValue > 0 { + if result.QueryName == "fetch_latency" { + fetchLatencyState = true + break + } + if result.QueryName == "consumer_lag" { + consumerLagState = true + break } - entries = append(entries, entry) - break - } else { - status = "1" } - } else if key == "fetchlatency" { - attribute = "kafka_consumer_fetch_latency_avg" - if intValue != 0 { - status = "0" - message = "Metric kafka_consumer_fetch_latency_avg is not present in the given time range." - } else { - status = "1" - } - } else if key == "grouplag" { - attribute = "kafka_consumer_group_lag" - if intValue != 0 { - status = "0" - message = "Metric kafka_consumer_group_lag is not present in the given time range." - } else { - status = "1" - } - } - entry := mq.OnboardingResponse{ - Attribute: attribute, - Message: message, - Status: status, } - entries = append(entries, entry) } } - sort.Slice(entries, func(i, j int) bool { - return entries[i].Attribute < entries[j].Attribute - }) + if !fetchLatencyState && !consumerLagState { + entries = append(entries, mq.OnboardingResponse{ + Attribute: "telemetry ingestion", + Message: "No data available in the given time range", + Status: "0", + }) + } + + if !fetchLatencyState { + entries = append(entries, mq.OnboardingResponse{ + Attribute: "kafka_consumer_fetch_latency_avg", + Message: "Metric kafka_consumer_fetch_latency_avg is not present in the given time range.", + Status: "0", + }) + } else { + entries = append(entries, mq.OnboardingResponse{ + Attribute: "kafka_consumer_fetch_latency_avg", + Status: "1", + }) + } + + if !consumerLagState { + entries = append(entries, mq.OnboardingResponse{ + Attribute: "kafka_consumer_group_lag", + Message: "Metric kafka_consumer_group_lag is not present in the given time range.", + Status: "0", + }) + } else { + entries = append(entries, mq.OnboardingResponse{ + Attribute: "kafka_consumer_group_lag", + Status: "1", + }) + } aH.Respond(w, entries) } diff --git a/pkg/query-service/app/integrations/messagingQueues/kafka/sql.go b/pkg/query-service/app/integrations/messagingQueues/kafka/sql.go index eeb1167f23..bf07316bb2 100644 --- a/pkg/query-service/app/integrations/messagingQueues/kafka/sql.go +++ b/pkg/query-service/app/integrations/messagingQueues/kafka/sql.go @@ -381,18 +381,3 @@ WHERE AND timestamp <= '%d';`, queueType, start, end) return query } - -func onboardKafkaSQL(start, end int64) string { - query := fmt.Sprintf(` -SELECT - COUNT(*) = 0 AS entries, - COUNT(IF(metric_name = 'kafka_consumer_fetch_latency_avg', 1, NULL)) = 0 AS fetchlatency, - COUNT(IF(metric_name = 'kafka_consumer_group_lag', 1, NULL)) = 0 AS grouplag -FROM - signoz_metrics.time_series_v4_1day -WHERE - metric_name IN ('kafka_consumer_fetch_latency_avg', 'kafka_consumer_group_lag') - AND unix_milli >= '%d' - AND unix_milli < '%d';`, start/1000000, end/1000000) - return query -} diff --git a/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go b/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go index a7d934d5a4..1de4a4bb5d 100644 --- a/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go +++ b/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go @@ -185,6 +185,60 @@ func buildBuilderQueriesNetwork(unixMilliStart, unixMilliEnd int64, attributeCac return bq, nil } +func BuildBuilderQueriesKafkaOnboarding(messagingQueue *MessagingQueue) (*v3.QueryRangeParamsV3, error) { + bq := make(map[string]*v3.BuilderQuery) + + unixMilliStart := messagingQueue.Start / 1000000 + unixMilliEnd := messagingQueue.End / 1000000 + + buiderQuery := &v3.BuilderQuery{ + QueryName: "fetch_latency", + StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd), + DataSource: v3.DataSourceMetrics, + AggregateAttribute: v3.AttributeKey{ + Key: "kafka_consumer_fetch_latency_avg", + }, + AggregateOperator: v3.AggregateOperatorCount, + Temporality: v3.Unspecified, + TimeAggregation: v3.TimeAggregationCount, + SpaceAggregation: v3.SpaceAggregationSum, + Expression: "fetch_latency", + } + bq["fetch_latency"] = buiderQuery + + buiderQuery = &v3.BuilderQuery{ + QueryName: "consumer_lag", + StepInterval: common.MinAllowedStepInterval(unixMilliStart, unixMilliEnd), + DataSource: v3.DataSourceMetrics, + AggregateAttribute: v3.AttributeKey{ + Key: "kafka_consumer_group_lag", + }, + AggregateOperator: v3.AggregateOperatorCount, + Temporality: v3.Unspecified, + TimeAggregation: v3.TimeAggregationCount, + SpaceAggregation: v3.SpaceAggregationSum, + Expression: "consumer_lag", + } + bq["consumer_lag"] = buiderQuery + + cq := &v3.CompositeQuery{ + QueryType: v3.QueryTypeBuilder, + BuilderQueries: bq, + PanelType: v3.PanelTypeTable, + } + + queryRangeParams := &v3.QueryRangeParamsV3{ + Start: unixMilliStart, + End: unixMilliEnd, + Step: defaultStepInterval, + CompositeQuery: cq, + Version: "v4", + FormatForWeb: true, + } + + return queryRangeParams, nil +} + func BuildQRParamsWithCache(messagingQueue *MessagingQueue, queryContext string, attributeCache *Clients) (*v3.QueryRangeParamsV3, error) { queueType := KafkaQueue @@ -302,8 +356,6 @@ func BuildClickHouseQuery(messagingQueue *MessagingQueue, queueType string, quer query = onboardProducersSQL(start, end, queueType) } else if queryContext == "onboard_consumers" { query = onboardConsumerSQL(start, end, queueType) - } else if queryContext == "onboard_kafka" { - query = onboardKafkaSQL(start, end) } return &v3.ClickHouseQuery{ Query: query, From b83b295318beadee5a7c390b7f21b80a4749faf4 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Thu, 31 Oct 2024 14:10:49 +0530 Subject: [PATCH 22/52] fix: frontend/package.json & frontend/yarn.lock to reduce vulnerabilities (#6266) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-HTTPPROXYMIDDLEWARE-8229906 - https://snyk.io/vuln/SNYK-JS-UPLOT-6209224 Co-authored-by: snyk-bot --- frontend/package.json | 4 +- frontend/yarn.lock | 262 ++++++++++++++++++++++++++++-------------- 2 files changed, 175 insertions(+), 91 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index c9fd8a0cf4..2b4b0953b0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -34,7 +34,7 @@ "@dnd-kit/core": "6.1.0", "@dnd-kit/modifiers": "7.0.0", "@dnd-kit/sortable": "8.0.0", - "@grafana/data": "^9.5.2", + "@grafana/data": "^11.2.3", "@mdx-js/loader": "2.3.0", "@mdx-js/react": "2.3.0", "@monaco-editor/react": "^4.3.1", @@ -76,7 +76,7 @@ "fontfaceobserver": "2.3.0", "history": "4.10.1", "html-webpack-plugin": "5.5.0", - "http-proxy-middleware": "2.0.6", + "http-proxy-middleware": "2.0.7", "i18next": "^21.6.12", "i18next-browser-languagedetector": "^6.1.3", "i18next-http-backend": "^1.3.2", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8d0ed82a89..3a9e672adb 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2330,10 +2330,10 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@braintree/sanitize-url@6.0.2": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.2.tgz#6110f918d273fe2af8ea1c4398a88774bb9fc12f" - integrity sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg== +"@braintree/sanitize-url@7.0.1": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.0.1.tgz#457233b0a18741b7711855044102b82bae7a070b" + integrity sha512-URg8UM6lfC9ZYqFipItRSxYJdgpU5d2Z4KnjsJ+rj6tgAmGme7E+PQNCiud8g0HDaZKMovu2qjfa0f5Ge0Vlsg== "@commitlint/cli@^16.3.0": version "16.3.0" @@ -2624,40 +2624,42 @@ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9" integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== -"@grafana/data@^9.5.2": - version "9.5.7" - resolved "https://registry.yarnpkg.com/@grafana/data/-/data-9.5.7.tgz#99df8c3917caf1f12a7f42258579b73b99f1aaa9" - integrity sha512-TrbQBU41pS1ir9GCkf3kayKlUCyzCvh0Sk7xbVNh6mHyDFLVDyfqsH9iXEL6V62u/n4tiApvUweHtfuRmfGQsg== +"@grafana/data@^11.2.3": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@grafana/data/-/data-11.3.0.tgz#18a75244aa4e22a23ac6f43f6ecc405fd0e18da8" + integrity sha512-AH7kJQRk1G0ccfAnCPfeQ/zJJ0kdkvpfjgTe40EJbj4S3m2mJ1lawYxd0VUsPgrh0DVezBZZkybInn+PT9jv4A== dependencies: - "@braintree/sanitize-url" "6.0.2" - "@grafana/schema" "9.5.7" + "@braintree/sanitize-url" "7.0.1" + "@grafana/schema" "11.3.0" "@types/d3-interpolate" "^3.0.0" + "@types/string-hash" "1.1.3" d3-interpolate "3.0.1" - date-fns "2.29.3" - dompurify "^2.4.3" - eventemitter3 "5.0.0" + date-fns "3.6.0" + dompurify "^3.0.0" + eventemitter3 "5.0.1" fast_array_intersect "1.1.0" history "4.10.1" lodash "4.17.21" - marked "4.2.12" - moment "2.29.4" - moment-timezone "0.5.41" - ol "7.2.2" - papaparse "5.3.2" - react-use "17.4.0" - regenerator-runtime "0.13.11" - rxjs "7.8.0" + marked "12.0.2" + marked-mangle "1.1.9" + moment "2.30.1" + moment-timezone "0.5.46" + ol "7.4.0" + papaparse "5.4.1" + react-use "17.5.1" + rxjs "7.8.1" + string-hash "^1.1.3" tinycolor2 "1.6.0" - tslib "2.5.0" - uplot "1.6.24" + tslib "2.7.0" + uplot "1.6.31" xss "^1.0.14" -"@grafana/schema@9.5.7": - version "9.5.7" - resolved "https://registry.yarnpkg.com/@grafana/schema/-/schema-9.5.7.tgz#be62b02df456e016ceaf1f14863a96dc4d0606ee" - integrity sha512-x8GNCnTOXcGOFDqvetc5VPNXh5fsoEomDjrleEgflLqu1jo1zE1mUq0swafkjjOC57rOM4XXbXQc950wkKo7jw== +"@grafana/schema@11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@grafana/schema/-/schema-11.3.0.tgz#30d57d3d696b5926f173048949bb994d72056329" + integrity sha512-KOgk0omDs8URuhL++ksgMDPMB49di8oCKqMNI10z3zSQniECUow+3Ggz1WeenReL7ajyJVG/O3rGVEaqteDMzg== dependencies: - tslib "2.5.0" + tslib "2.7.0" "@humanwhocodes/config-array@^0.5.0": version "0.5.0" @@ -2988,6 +2990,11 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@jridgewell/sourcemap-codec@^1.4.15": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -4526,6 +4533,11 @@ resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/string-hash@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/string-hash/-/string-hash-1.1.3.tgz#8d9a73cf25574d45daf11e3ae2bf6b50e69aa212" + integrity sha512-p6skq756fJWiA59g2Uss+cMl6tpoDGuCBuxG0SI1t0NwJmYOU66LAMS6QiCgu7cUh3/hYCaMl5phcCW1JP5wOA== + "@types/styled-components@^5.1.4": version "5.1.26" resolved "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz" @@ -7220,6 +7232,11 @@ csstype@^3.0.10, csstype@^3.0.2, csstype@^3.0.6: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + custom-event-polyfill@^1.0.6: version "1.0.7" resolved "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz" @@ -7434,10 +7451,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@2.29.3: - version "2.29.3" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" - integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== +date-fns@3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf" + integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== dayjs@^1.10.7, dayjs@^1.11.1: version "1.11.7" @@ -7762,10 +7779,10 @@ dompurify@3.1.3: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.3.tgz#cfe3ce4232c216d923832f68f2aa18b2fb9bd223" integrity sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng== -dompurify@^2.4.3: - version "2.5.7" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.7.tgz#6e0d36b9177db5a99f18ade1f28579db5ab839d7" - integrity sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q== +dompurify@^3.0.0: + version "3.1.7" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.7.tgz#711a8c96479fb6ced93453732c160c3c72418a6a" + integrity sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ== domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" @@ -8491,11 +8508,6 @@ event-source-polyfill@1.0.31: resolved "https://registry.npmjs.org/event-source-polyfill/-/event-source-polyfill-1.0.31.tgz" integrity sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA== -eventemitter3@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.0.tgz#084eb7f5b5388df1451e63f4c2aafd71b217ccb3" - integrity sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg== - eventemitter3@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -9620,7 +9632,18 @@ http-proxy-agent@^4.0.1: agent-base "6" debug "4" -http-proxy-middleware@2.0.6, http-proxy-middleware@^2.0.3: +http-proxy-middleware@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy-middleware@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== @@ -9805,6 +9828,13 @@ inline-style-prefixer@^6.0.0: css-in-js-utils "^3.1.0" fast-loops "^1.1.3" +inline-style-prefixer@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8" + integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw== + dependencies: + css-in-js-utils "^3.1.0" + inquirer@^8.2.0: version "8.2.6" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.6.tgz#733b74888195d8d400a67ac332011b5fae5ea562" @@ -11432,10 +11462,15 @@ markdown-table@^3.0.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd" integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== -marked@4.2.12: - version "4.2.12" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.2.12.tgz#d69a64e21d71b06250da995dcd065c11083bebb5" - integrity sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw== +marked-mangle@1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/marked-mangle/-/marked-mangle-1.1.9.tgz#ee7a4a1e318a47c722c905e6472f346c6f7a08a6" + integrity sha512-eLTXr1xQzba/WZp/trPS0HkR9W02ifasH6IWPrBv++eO2m8POiwV4muQ6Tof2C5Fhdo3z8ggXs6VGw1f931Vsg== + +marked@12.0.2: + version "12.0.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-12.0.2.tgz#b31578fe608b599944c69807b00f18edab84647e" + integrity sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q== match-sorter@^6.0.2: version "6.3.1" @@ -12245,14 +12280,19 @@ mkdirp@^0.5.6: dependencies: minimist "^1.2.6" -moment-timezone@0.5.41: - version "0.5.41" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.41.tgz#a7ad3285fd24aaf5f93b8119a9d749c8039c64c5" - integrity sha512-e0jGNZDOHfBXJGz8vR/sIMXvBIGJJcqFjmlg9lmE+5KX1U7/RZNMswfD8nKnNCnQdKTIj50IaRKwl1fvMLyyRg== +moment-timezone@0.5.46: + version "0.5.46" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.46.tgz#a21aa6392b3c6b3ed916cd5e95858a28d893704a" + integrity sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw== dependencies: moment "^2.29.4" -moment@2.29.4, moment@^2.29.4: +moment@2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +moment@^2.29.4: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -12334,6 +12374,20 @@ nano-css@^5.3.1: stacktrace-js "^2.0.2" stylis "^4.0.6" +nano-css@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.2.tgz#584884ddd7547278f6d6915b6805069742679a32" + integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + css-tree "^1.1.2" + csstype "^3.1.2" + fastest-stable-stringify "^2.0.2" + inline-style-prefixer "^7.0.1" + rtl-css-js "^1.16.1" + stacktrace-js "^2.0.2" + stylis "^4.3.0" + nano-time@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz" @@ -12640,22 +12694,34 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -ol-mapbox-style@^9.2.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz#38a4f7abc8f0a94f378dcdb7cefdcc69ca3f6287" - integrity sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg== +ol-mapbox-style@^10.1.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-10.7.0.tgz#8837912da2a16fbd22992d76cbc4f491c838b973" + integrity sha512-S/UdYBuOjrotcR95Iq9AejGYbifKeZE85D9VtH11ryJLQPTZXZSW1J5bIXcr4AlAH6tyjPPHTK34AdkwB32Myw== dependencies: "@mapbox/mapbox-gl-style-spec" "^13.23.1" mapbox-to-css-font "^2.4.1" + ol "^7.3.0" -ol@7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/ol/-/ol-7.2.2.tgz#d675a1525fd995a29a70a9a9fa9c3a9bc827aa39" - integrity sha512-eqJ1hhVQQ3Ap4OhYq9DRu5pz9RMpLhmoTauDoIqpn7logVi1AJE+lXjEHrPrTSuZYjtFbMgqr07sxoLNR65nrw== +ol@7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/ol/-/ol-7.4.0.tgz#935436c0843d1f939972e076d4fcb130530ce9d7" + integrity sha512-bgBbiah694HhC0jt8ptEFNRXwgO8d6xWH3G97PCg4bmn9Li5nLLbi59oSrvqUI6VPVwonPQF1YcqJymxxyMC6A== dependencies: earcut "^2.2.3" geotiff "^2.0.7" - ol-mapbox-style "^9.2.0" + ol-mapbox-style "^10.1.0" + pbf "3.2.1" + rbush "^3.0.1" + +ol@^7.3.0: + version "7.5.2" + resolved "https://registry.yarnpkg.com/ol/-/ol-7.5.2.tgz#2e40a16b45331dbee86ca86876fcc7846be0dbb7" + integrity sha512-HJbb3CxXrksM6ct367LsP3N+uh+iBBMdP3DeGGipdV9YAYTP0vTJzqGnoqQ6C2IW4qf8krw9yuyQbc9fjOIaOQ== + dependencies: + earcut "^2.2.3" + geotiff "^2.0.7" + ol-mapbox-style "^10.1.0" pbf "3.2.1" rbush "^3.0.1" @@ -12837,11 +12903,6 @@ pako@~1.0.2: resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -papaparse@5.3.2: - version "5.3.2" - resolved "https://registry.npmjs.org/papaparse/-/papaparse-5.3.2.tgz" - integrity sha512-6dNZu0Ki+gyV0eBsFKJhYr+MdQYAzFUGlBMNj3GNrmHxmz1lfRa24CjFObPXtjcetlOv5Ad299MhIK0znp3afw== - papaparse@5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127" @@ -14371,7 +14432,27 @@ react-use-measure@^2.0.4: dependencies: debounce "^1.2.1" -react-use@17.4.0, react-use@^17.3.2: +react-use@17.5.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.5.1.tgz#19fc2ae079775d8450339e9fa8dbe25b17f2263c" + integrity sha512-LG/uPEVRflLWMwi3j/sZqR00nF6JGqTTDblkXK2nzXsIvij06hXl1V/MZIlwj1OKIQUtlh1l9jK8gLsRyCQxMg== + dependencies: + "@types/js-cookie" "^2.2.6" + "@xobotyi/scrollbar-width" "^1.9.5" + copy-to-clipboard "^3.3.1" + fast-deep-equal "^3.1.3" + fast-shallow-equal "^1.0.0" + js-cookie "^2.2.1" + nano-css "^5.6.2" + react-universal-interface "^0.6.2" + resize-observer-polyfill "^1.5.1" + screenfull "^5.1.0" + set-harmonic-interval "^1.0.1" + throttle-debounce "^3.0.1" + ts-easing "^0.2.0" + tslib "^2.1.0" + +react-use@^17.3.2: version "17.4.0" resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.0.tgz#cefef258b0a6c534a5c8021c2528ac6e1a4cdc6d" integrity sha512-TgbNTCA33Wl7xzIJegn1HndB4qTS9u03QUwyNycUnXaweZkE4Kq2SB+Yoxx8qbshkZGYBDvUXbXWRUmQDcZZ/Q== @@ -14525,16 +14606,16 @@ regenerate@^1.4.2: resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== -regenerator-runtime@0.13.11, regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" @@ -14904,7 +14985,7 @@ robust-predicates@^3.0.2: resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771" integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== -rtl-css-js@^1.14.0: +rtl-css-js@^1.14.0, rtl-css-js@^1.16.1: version "1.16.1" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz" integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== @@ -14928,14 +15009,7 @@ rw@^1.3.3: resolved "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz" integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== -rxjs@7.8.0: - version "7.8.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" - integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== - dependencies: - tslib "^2.1.0" - -rxjs@^7.5.5: +rxjs@7.8.1, rxjs@^7.5.5: version "7.8.1" resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz" integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== @@ -15575,6 +15649,11 @@ string-convert@^0.2.0: resolved "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== +string-hash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b" + integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -15778,6 +15857,11 @@ stylis@^4.0.13, stylis@^4.0.6: resolved "https://registry.npmjs.org/stylis/-/stylis-4.1.3.tgz" integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== +stylis@^4.3.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.4.tgz#ca5c6c4a35c4784e4e93a2a24dc4e9fa075250a4" + integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== + stylus@^0.59.0: version "0.59.0" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.59.0.tgz#a344d5932787142a141946536d6e24e6a6be7aa6" @@ -16204,10 +16288,10 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.5.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0: - version "2.5.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" - integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== +tslib@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== tslib@^1.8.1: version "1.14.1" @@ -16219,6 +16303,11 @@ tslib@^2.0.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0: + version "2.5.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz" @@ -16583,11 +16672,6 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" -uplot@1.6.24: - version "1.6.24" - resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.24.tgz#dfa213fa7da92763261920ea972ed1a5f9f6af12" - integrity sha512-WpH2BsrFrqxkMu+4XBvc0eCDsRBhzoq9crttYeSI0bfxpzR5YoSVzZXOKFVWcVC7sp/aDXrdDPbDZGCtck2PVg== - uplot@1.6.31: version "1.6.31" resolved "https://registry.yarnpkg.com/uplot/-/uplot-1.6.31.tgz#092a4b586590e9794b679e1df885a15584b03698" From 03c193d5a128d58c60ec1eb182e0ba6e4c0ceff7 Mon Sep 17 00:00:00 2001 From: Shaheer Kochai Date: Thu, 31 Oct 2024 13:30:34 +0430 Subject: [PATCH 23/52] chore: upgrade axios from 1.7.4 to 1.7.7 (#6291) --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 2b4b0953b0..8a6fc23dee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -51,7 +51,7 @@ "ansi-to-html": "0.7.2", "antd": "5.11.0", "antd-table-saveas-excel": "2.2.1", - "axios": "1.7.4", + "axios": "1.7.7", "babel-eslint": "^10.1.0", "babel-jest": "^29.6.4", "babel-loader": "9.1.3", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 3a9e672adb..8af8e48e31 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -5590,10 +5590,10 @@ axe-core@^4.6.2: resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz" integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== -axios@1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" - integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== +axios@1.7.7: + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" From 2112047a0240688d1e4dfba0a8480807eaa72c00 Mon Sep 17 00:00:00 2001 From: Ankit Nayan Date: Thu, 31 Oct 2024 15:54:28 +0530 Subject: [PATCH 24/52] [Snyk] Security upgrade alpine from 3.18.5 to 3.20.3 (#6237) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-6913411 - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265 - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419 - https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6152404 - https://snyk.io/vuln/SNYK-ALPINE318-OPENSSL-6152404 Co-authored-by: snyk-bot --- pkg/query-service/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/query-service/Dockerfile b/pkg/query-service/Dockerfile index 61fa02d698..a870ecceef 100644 --- a/pkg/query-service/Dockerfile +++ b/pkg/query-service/Dockerfile @@ -1,5 +1,5 @@ # use a minimal alpine image -FROM alpine:3.18.5 +FROM alpine:3.20.3 # Add Maintainer Info LABEL maintainer="signoz" From c177230cceb8dbf03dce20bf355091fee87265ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 16:44:26 +0530 Subject: [PATCH 25/52] chore(deps): bump webpack from 5.88.2 to 5.94.0 in /frontend (#5813) Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- frontend/package.json | 2 +- frontend/yarn.lock | 454 +++++++++++++----------------------------- 2 files changed, 143 insertions(+), 313 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 8a6fc23dee..82fda7af0a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -126,7 +126,7 @@ "uplot": "1.6.31", "uuid": "^8.3.2", "web-vitals": "^0.2.4", - "webpack": "5.88.2", + "webpack": "5.94.0", "webpack-dev-server": "^4.15.1", "webpack-retry-chunk-load-plugin": "3.1.1", "xstate": "^4.31.0" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8af8e48e31..e3dcb0b41f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2964,14 +2964,6 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/source-map@^0.3.2": - version "0.3.3" - resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz" - integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - "@jridgewell/source-map@^0.3.3": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91" @@ -3019,7 +3011,7 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -4074,22 +4066,6 @@ dependencies: "@types/trusted-types" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.37.0" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz" - integrity sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - "@types/estree-jsx@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.0.tgz#7bfc979ab9f692b492017df42520f7f765e98df1" @@ -4097,10 +4073,10 @@ dependencies: "@types/estree" "*" -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz" - integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/event-source-polyfill@^1.0.0": version "1.0.1" @@ -4233,7 +4209,7 @@ resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== -"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -4932,125 +4908,125 @@ resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.4.4.tgz#26e4e62a5fbfb39c25e9e54d21eeb852f1c83a7a" integrity sha512-abSgiVRhfjfl3JALR/cSuBl74hGJ3SePgf1mKzodf1eMWLwHZbfEGxT2cNJSsNiw44jEgrO7bNkhchaWA7RwNw== -"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz" - integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ== +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: - "@webassemblyjs/helper-numbers" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" -"@webassemblyjs/floating-point-hex-parser@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz" - integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ== +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== -"@webassemblyjs/helper-api-error@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz" - integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA== +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz" - integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg== +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== -"@webassemblyjs/helper-numbers@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz" - integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA== +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz" - integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA== +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz" - integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA== +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" -"@webassemblyjs/ieee754@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz" - integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg== +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz" - integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ== +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz" - integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ== +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz" - integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ== +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/helper-wasm-section" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-opt" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" - "@webassemblyjs/wast-printer" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" -"@webassemblyjs/wasm-gen@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz" - integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA== +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wasm-opt@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz" - integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw== +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz" - integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew== +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" -"@webassemblyjs/wast-printer@1.11.5": - version "1.11.5" - resolved "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz" - integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA== +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== dependencies: - "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^1.2.0": @@ -5137,15 +5113,10 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== - -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn-jsx@^5.0.0, acorn-jsx@^5.3.1: version "5.3.2" @@ -5167,17 +5138,7 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.0, acorn@^8.8.2: - version "8.10.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== - -acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: - version "8.8.2" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - -acorn@^8.8.1: +acorn@^8.0.0, acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.1, acorn@^8.8.2: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -6177,37 +6138,7 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5: - version "4.21.5" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== - dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" - -browserslist@^4.21.10, browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -browserslist@^4.22.2: - version "4.22.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" - integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== - dependencies: - caniuse-lite "^1.0.30001580" - electron-to-chromium "^1.4.648" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -browserslist@^4.23.0: +browserslist@^4.0.0, browserslist@^4.21.10, browserslist@^4.21.3, browserslist@^4.21.4, browserslist@^4.21.5, browserslist@^4.21.9, browserslist@^4.22.2, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -6338,21 +6269,11 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449: +caniuse-lite@^1.0.0: version "1.0.30001481" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz" integrity sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ== -caniuse-lite@^1.0.30001517: - version "1.0.30001525" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz#d2e8fdec6116ffa36284ca2c33ef6d53612fe1c8" - integrity sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q== - -caniuse-lite@^1.0.30001580: - version "1.0.30001587" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz#a0bce920155fa56a1885a69c74e1163fc34b4881" - integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== - caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: version "1.0.30001621" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz#4adcb443c8b9c8303e04498318f987616b8fea2e" @@ -7857,21 +7778,6 @@ ee-first@1.1.1: resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -electron-to-chromium@^1.4.284: - version "1.4.376" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.376.tgz" - integrity sha512-TFeOKd98TpJzRHkr4Aorn16QkMnuCQuGAE6IZ0wYF+qkbSfMPqjplvRppR02tMUpVxZz8nyBNvVm9lIZsqrbPQ== - -electron-to-chromium@^1.4.477: - version "1.4.508" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.508.tgz#5641ff2f5ba11df4bd960fe6a2f9f70aa8b9af96" - integrity sha512-FFa8QKjQK/A5QuFr2167myhMesGrhlOBD+3cYNxO9/S4XzHEXesyTD/1/xF644gC8buFPz3ca6G1LOQD0tZrrg== - -electron-to-chromium@^1.4.648: - version "1.4.667" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.667.tgz#2767d998548e5eeeaf8bdaffd67b56796bfbed3d" - integrity sha512-66L3pLlWhTNVUhnmSA5+qDM3fwnXsM6KAqE36e2w4KN0g6pkEtlT5bs41FQtQwVwKnfhNBXiWRLPs30HSxd7Kw== - electron-to-chromium@^1.4.668: version "1.4.777" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz#f846fbba23fd11b3c6f97848cdda94896fdb8baf" @@ -7907,18 +7813,10 @@ encodeurl@~1.0.2: resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== -enhanced-resolve@^5.13.0, enhanced-resolve@^5.7.0: - version "5.13.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz" - integrity sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== +enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -9142,7 +9040,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -12512,21 +12410,11 @@ node-int64@^0.4.0: resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== - normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -15101,16 +14989,7 @@ schema-utils@^2.7.0: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz" - integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.2.0: +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== @@ -15119,17 +14998,7 @@ schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.1.tgz" - integrity sha512-lELhBAAly9NowEsX0yZBlw9ahZG+sK/1RJ21EpzdYHKEs13Vku3LJ+MIPhh4sMs0oCCeufZQEQbMekiA4vuVIQ== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -schema-utils@^4.0.1: +schema-utils@^4.0.0, schema-utils@^4.0.1: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -15978,16 +15847,16 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.7: - version "5.3.7" - resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz" - integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== +terser-webpack-plugin@^5.2.5, terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.5" + terser "^5.26.0" terser@^5.10.0: version "5.19.4" @@ -15999,13 +15868,13 @@ terser@^5.10.0: commander "^2.20.0" source-map-support "~0.5.20" -terser@^5.16.5: - version "5.17.1" - resolved "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz" - integrity sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw== +terser@^5.26.0: + version "5.32.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.32.0.tgz#ee811c0d2d6b741c1cc34a2bc5bcbfc1b5b1f96c" + integrity sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ== dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" commander "^2.20.0" source-map-support "~0.5.20" @@ -16656,14 +16525,6 @@ unplugin@1.0.1: webpack-sources "^3.2.3" webpack-virtual-modules "^0.5.0" -update-browserslist-db@^1.0.10, update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" @@ -16920,10 +16781,10 @@ walker@^1.0.7, walker@^1.0.8: dependencies: makeerror "1.0.12" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -17145,64 +17006,33 @@ webpack-virtual-modules@^0.5.0: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== -webpack@5.88.2: - version "5.88.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e" - integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ== +webpack@5.94.0, webpack@^5, webpack@^5.1.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" - webpack-sources "^3.2.3" - -webpack@^5, webpack@^5.1.0: - version "5.81.0" - resolved "https://registry.npmjs.org/webpack/-/webpack-5.81.0.tgz" - integrity sha512-AAjaJ9S4hYCVODKLQTgG5p5e11hiMawBwV2v8MYLE0C/6UAGLuAF4n1qa9GOwdxnicaP+5k6M5HrLmD4+gIB8Q== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" - acorn "^8.7.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.13.0" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.2" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: From b770fc245745cc2c8504b4911b33712b24939f42 Mon Sep 17 00:00:00 2001 From: Shivanshu Raj Shrivastava Date: Thu, 31 Oct 2024 20:11:50 +0530 Subject: [PATCH 26/52] fix: typo (#6334) --- .../app/integrations/messagingQueues/kafka/translator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go b/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go index 1de4a4bb5d..2731bd5b95 100644 --- a/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go +++ b/pkg/query-service/app/integrations/messagingQueues/kafka/translator.go @@ -364,7 +364,7 @@ func BuildClickHouseQuery(messagingQueue *MessagingQueue, queueType string, quer func buildCompositeQuery(chq *v3.ClickHouseQuery, queryContext string) (*v3.CompositeQuery, error) { - if queryContext == "producer-consumer-eva" { + if queryContext == "producer-consumer-eval" { return &v3.CompositeQuery{ QueryType: v3.QueryTypeClickHouseSQL, ClickHouseQueries: map[string]*v3.ClickHouseQuery{queryContext: chq}, From 580f0b816e9282a837f10642c490bb7a21c68f3c Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Fri, 1 Nov 2024 13:52:13 +0530 Subject: [PATCH 27/52] fix: issues with resource query builder w.r.t quotes (#6318) --- .../app/logs/v4/query_builder.go | 2 +- .../app/resource/resource_query_builder.go | 22 +++++++++---------- .../resource/resource_query_builder_test.go | 14 ++++++------ pkg/query-service/utils/format.go | 11 +++++++++- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go index e38fb94934..3952d0e7e1 100644 --- a/pkg/query-service/app/logs/v4/query_builder.go +++ b/pkg/query-service/app/logs/v4/query_builder.go @@ -149,7 +149,7 @@ func buildAttributeFilter(item v3.FilterItem) (string, error) { return fmt.Sprintf(logsOp, keyName, fmtVal), nil case v3.FilterOperatorContains, v3.FilterOperatorNotContains: // we also want to treat %, _ as literals for contains - val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value)) + val := utils.QuoteEscapedStringForContains(fmt.Sprintf("%s", item.Value), false) // for body the contains is case insensitive if keyName == BODY { logsOp = strings.Replace(logsOp, "ILIKE", "LIKE", 1) // removing i from ilike and not ilike diff --git a/pkg/query-service/app/resource/resource_query_builder.go b/pkg/query-service/app/resource/resource_query_builder.go index bbf9310386..c03807b130 100644 --- a/pkg/query-service/app/resource/resource_query_builder.go +++ b/pkg/query-service/app/resource/resource_query_builder.go @@ -49,7 +49,7 @@ func buildResourceFilter(logsOp string, key string, op v3.FilterOperator, value case v3.FilterOperatorContains, v3.FilterOperatorNotContains: // this is required as clickhouseFormattedValue add's quotes to the string // we also want to treat %, _ as literals for contains - escapedStringValue := utils.QuoteEscapedStringForContains(lowerValue) + escapedStringValue := utils.QuoteEscapedStringForContains(lowerValue, false) return fmt.Sprintf("%s %s '%%%s%%'", lowerSearchKey, logsOp, escapedStringValue) case v3.FilterOperatorLike, v3.FilterOperatorNotLike: // this is required as clickhouseFormattedValue add's quotes to the string @@ -92,7 +92,7 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter // if there are no values to filter on, return an empty string if len(values) > 0 { for _, v := range values { - value := utils.QuoteEscapedStringForContains(v) + value := utils.QuoteEscapedStringForContains(v, true) conditions = append(conditions, fmt.Sprintf("labels %s '%%\"%s\":\"%s\"%%'", sqlOp, key, value)) } return "(" + strings.Join(conditions, separator) + ")" @@ -110,24 +110,24 @@ func buildIndexFilterForInOperator(key string, op v3.FilterOperator, value inter func buildResourceIndexFilter(key string, op v3.FilterOperator, value interface{}) string { // not using clickhouseFormattedValue as we don't wan't the quotes strVal := fmt.Sprintf("%s", value) - formattedValueEscapedForContains := strings.ToLower(utils.QuoteEscapedStringForContains(strVal)) - formattedValueEscaped := utils.QuoteEscapedString(strVal) - formattedValueEscapedLower := strings.ToLower(formattedValueEscaped) + fmtValEscapedForContains := utils.QuoteEscapedStringForContains(strVal, true) + fmtValEscapedForContainsLower := strings.ToLower(fmtValEscapedForContains) + fmtValEscapedLower := strings.ToLower(utils.QuoteEscapedString(strVal)) // add index filters switch op { case v3.FilterOperatorContains: - return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, fmtValEscapedForContainsLower) case v3.FilterOperatorNotContains: - return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, formattedValueEscapedForContains) + return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, fmtValEscapedForContainsLower) case v3.FilterOperatorLike: - return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, formattedValueEscapedLower) + return fmt.Sprintf("lower(labels) like '%%%s%%%s%%'", key, fmtValEscapedLower) case v3.FilterOperatorNotLike: - return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, formattedValueEscapedLower) + return fmt.Sprintf("lower(labels) not like '%%%s%%%s%%'", key, fmtValEscapedLower) case v3.FilterOperatorEqual: - return fmt.Sprintf("labels like '%%%s%%%s%%'", key, formattedValueEscaped) + return fmt.Sprintf("labels like '%%%s%%%s%%'", key, fmtValEscapedForContains) case v3.FilterOperatorNotEqual: - return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, formattedValueEscaped) + return fmt.Sprintf("labels not like '%%%s%%%s%%'", key, fmtValEscapedForContains) case v3.FilterOperatorRegex, v3.FilterOperatorNotRegex: // don't try to do anything for regex. return "" diff --git a/pkg/query-service/app/resource/resource_query_builder_test.go b/pkg/query-service/app/resource/resource_query_builder_test.go index f390ff9c6e..ff9c251882 100644 --- a/pkg/query-service/app/resource/resource_query_builder_test.go +++ b/pkg/query-service/app/resource/resource_query_builder_test.go @@ -138,9 +138,9 @@ func Test_buildIndexFilterForInOperator(t *testing.T) { args: args{ key: "service.name", op: v3.FilterOperatorNotIn, - value: "application'\"_s", + value: `application'"_s`, }, - want: `(labels not like '%"service.name":"application\'"\_s"%')`, + want: `(labels not like '%"service.name":"application\'\\\\"\_s"%')`, }, } for _, tt := range tests { @@ -231,9 +231,9 @@ func Test_buildResourceIndexFilter(t *testing.T) { args: args{ key: "service.name", op: v3.FilterOperatorEqual, - value: "Application", + value: `Application"`, }, - want: `labels like '%service.name%Application%'`, + want: `labels like '%service.name%Application\\\\"%'`, }, } for _, tt := range tests { @@ -319,7 +319,7 @@ func Test_buildResourceFiltersFromFilterItems(t *testing.T) { Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorContains, - Value: "test1", + Value: `test1"`, }, }, }, @@ -327,8 +327,8 @@ func Test_buildResourceFiltersFromFilterItems(t *testing.T) { want: []string{ "simpleJSONExtractString(labels, 'service.name') = 'test'", "labels like '%service.name%test%'", - "simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1%'", - "lower(labels) like '%namespace%test1%'", + `simpleJSONExtractString(lower(labels), 'namespace') LIKE '%test1"%'`, + `lower(labels) like '%namespace%test1\\\\"%'`, }, wantErr: false, }, diff --git a/pkg/query-service/utils/format.go b/pkg/query-service/utils/format.go index f09f4dffd6..963abac219 100644 --- a/pkg/query-service/utils/format.go +++ b/pkg/query-service/utils/format.go @@ -156,9 +156,18 @@ func QuoteEscapedString(str string) string { return str } -func QuoteEscapedStringForContains(str string) string { +func QuoteEscapedStringForContains(str string, isIndex bool) string { // https: //clickhouse.com/docs/en/sql-reference/functions/string-search-functions#like str = QuoteEscapedString(str) + + // we are adding this because if a string contains quote `"` it will be stored as \" in clickhouse + // to query that using like our query should be \\\\" + if isIndex { + // isIndex is true means that the extra slash is present + // [\"a\",\"b\",\"sdf\"] + str = strings.ReplaceAll(str, `"`, `\\\\"`) + } + str = strings.ReplaceAll(str, `%`, `\%`) str = strings.ReplaceAll(str, `_`, `\_`) return str From 92cdb36879b395002216d6321d104fe731afc28d Mon Sep 17 00:00:00 2001 From: Shaheer Kochai Date: Fri, 1 Nov 2024 16:03:59 +0430 Subject: [PATCH 28/52] fix: redirect to docs on clicking alert setup guide in create alert page (#6265) --- .../src/container/FormAlertRules/index.tsx | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/FormAlertRules/index.tsx b/frontend/src/container/FormAlertRules/index.tsx index 9b33198ff7..05c4149d73 100644 --- a/frontend/src/container/FormAlertRules/index.tsx +++ b/frontend/src/container/FormAlertRules/index.tsx @@ -73,6 +73,19 @@ export enum AlertDetectionTypes { ANOMALY_DETECTION_ALERT = 'anomaly_rule', } +const ALERT_SETUP_GUIDE_URLS: Record = { + [AlertTypes.METRICS_BASED_ALERT]: + 'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page', + [AlertTypes.LOGS_BASED_ALERT]: + 'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page', + [AlertTypes.TRACES_BASED_ALERT]: + 'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page', + [AlertTypes.EXCEPTIONS_BASED_ALERT]: + 'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page', + [AlertTypes.ANOMALY_BASED_ALERT]: + 'https://signoz.io/docs/alerts-management/anomaly-based-alerts/?utm_source=product&utm_medium=alert-creation-page', +}; + // eslint-disable-next-line sonarjs/cognitive-complexity function FormAlertRules({ alertType, @@ -702,6 +715,29 @@ function FormAlertRules({ const isRuleCreated = !ruleId || ruleId === 0; + function handleRedirection(option: AlertTypes): void { + let url; + if ( + option === AlertTypes.METRICS_BASED_ALERT && + alertTypeFromURL === AlertDetectionTypes.ANOMALY_DETECTION_ALERT + ) { + url = ALERT_SETUP_GUIDE_URLS[AlertTypes.ANOMALY_BASED_ALERT]; + } else { + url = ALERT_SETUP_GUIDE_URLS[option]; + } + + if (url) { + logEvent('Alert: Check example alert clicked', { + dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes], + isNewRule: !ruleId || ruleId === 0, + ruleId, + queryType: currentQuery.queryType, + link: url, + }); + window.open(url, '_blank'); + } + } + useEffect(() => { if (!isRuleCreated) { logEvent('Alert: Edit page visited', { @@ -752,7 +788,11 @@ function FormAlertRules({ )}
-
From 7b18c3ba06625012d42d8f2929cf1ce0dbb1f3c0 Mon Sep 17 00:00:00 2001 From: Shivanshu Raj Shrivastava Date: Fri, 1 Nov 2024 21:19:58 +0530 Subject: [PATCH 29/52] enable scenario 4 on staging (#6269) * fix: enable env at docker compose --- .github/workflows/staging-deployment.yaml | 2 +- deploy/docker/clickhouse-setup/docker-compose.testing.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-deployment.yaml b/.github/workflows/staging-deployment.yaml index 4918cf54d2..bbdbe32531 100644 --- a/.github/workflows/staging-deployment.yaml +++ b/.github/workflows/staging-deployment.yaml @@ -31,7 +31,6 @@ jobs: GCP_ZONE: ${{ secrets.GCP_ZONE }} GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }} CLOUDSDK_CORE_DISABLE_PROMPTS: 1 - KAFKA_SPAN_EVAL: true run: | read -r -d '' COMMAND < Date: Fri, 1 Nov 2024 22:51:09 +0530 Subject: [PATCH 30/52] fix: add safety check to check if anomaly rule in uplot chart options (#6343) --- frontend/src/lib/uPlotLib/getUplotChartOptions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts index 78617669a2..9a42a6df94 100644 --- a/frontend/src/lib/uPlotLib/getUplotChartOptions.ts +++ b/frontend/src/lib/uPlotLib/getUplotChartOptions.ts @@ -163,7 +163,8 @@ export const getUPlotChartOptions = ({ const stackBarChart = stackChart && isUndefined(hiddenGraph); - const isAnomalyRule = apiResponse?.data?.newResult?.data?.result[0].isAnomaly; + const isAnomalyRule = + apiResponse?.data?.newResult?.data?.result[0]?.isAnomaly || false; const series = getStackedSeries(apiResponse?.data?.result || []); From c7d0598ec0d4e248cb09a5491ed5cddae30aa2f5 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Fri, 1 Nov 2024 23:55:29 +0530 Subject: [PATCH 31/52] feat: improve async handling for org onboarding cases (#6342) --- frontend/src/AppRoutes/Private.tsx | 121 +++++---- .../OnboardingContainer.tsx | 2 +- .../InviteTeamMembers/InviteTeamMembers.tsx | 79 +++--- .../OnboardingQuestionaire.styles.scss | 9 + .../OptimiseSignozNeeds.tsx | 2 +- .../OnboardingQuestionaire/index.tsx | 240 ++++++------------ frontend/src/pages/SignUp/SignUp.tsx | 2 +- 7 files changed, 205 insertions(+), 250 deletions(-) diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx index 3956676ec7..645c28095c 100644 --- a/frontend/src/AppRoutes/Private.tsx +++ b/frontend/src/AppRoutes/Private.tsx @@ -9,7 +9,7 @@ import ROUTES from 'constants/routes'; import useLicense from 'hooks/useLicense'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { isEmpty } from 'lodash-es'; +import { isEmpty, isNull } from 'lodash-es'; import { ReactChild, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useQuery } from 'react-query'; @@ -35,13 +35,18 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { const location = useLocation(); const { pathname } = location; - const { org, orgPreferences } = useSelector( - (state) => state.app, - ); + const [isLoading, setIsLoading] = useState(true); - const [isOnboardingComplete, setIsOnboardingComplete] = useState< - boolean | null - >(null); + const { + org, + orgPreferences, + user, + role, + isUserFetching, + isUserFetchingError, + isLoggedIn: isLoggedInState, + isFetchingOrgPreferences, + } = useSelector((state) => state.app); const mapRoutes = useMemo( () => @@ -56,30 +61,19 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { [pathname], ); - useEffect(() => { - if (orgPreferences && !isEmpty(orgPreferences)) { - const onboardingPreference = orgPreferences?.find( + const isOnboardingComplete = useMemo( + () => + orgPreferences?.find( (preference: Record) => preference.key === 'ORG_ONBOARDING', - ); - - if (onboardingPreference) { - setIsOnboardingComplete(onboardingPreference.value); - } - } - }, [orgPreferences]); + )?.value, + [orgPreferences], + ); const { data: licensesData, isFetching: isFetchingLicensesData, } = useLicense(); - const { - isUserFetching, - isUserFetchingError, - isLoggedIn: isLoggedInState, - isFetchingOrgPreferences, - } = useSelector((state) => state.app); - const { t } = useTranslation(['common']); const localStorageUserAuthToken = getInitialUserTokenRefreshToken(); @@ -135,7 +129,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { // Check if the onboarding should be shown based on the org users and onboarding completion status, wait for org users and preferences to load const shouldShowOnboarding = (): boolean => { // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers) { + + if (!isLoadingOrgUsers && !isFetchingOrgPreferences) { const isFirstUser = checkFirstTimeUser(); // Redirect to get started if it's not the first user or if the onboarding is complete @@ -145,6 +140,26 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { return false; }; + const handleRedirectForOrgOnboarding = (key: string): void => { + if ( + isLoggedInState && + !isFetchingOrgPreferences && + !isLoadingOrgUsers && + !isEmpty(orgUsers?.payload) && + !isNull(orgPreferences) + ) { + if (key === 'ONBOARDING' && isOnboardingComplete) { + history.push(ROUTES.APPLICATION); + } + + const isFirstTimeUser = checkFirstTimeUser(); + + if (isFirstTimeUser && !isOnboardingComplete) { + history.push(ROUTES.ONBOARDING); + } + } + }; + const handleUserLoginIfTokenPresent = async ( key: keyof typeof ROUTES, ): Promise => { @@ -166,15 +181,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { response.payload.refreshJwt, ); - const showOnboarding = shouldShowOnboarding(); - - if ( - userResponse && - showOnboarding && - userResponse.payload.role === 'ADMIN' - ) { - history.push(ROUTES.ONBOARDING); - } + handleRedirectForOrgOnboarding(key); if ( userResponse && @@ -203,7 +210,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { ) { handleUserLoginIfTokenPresent(key); } else { - // user does have localstorage values + handleRedirectForOrgOnboarding(key); navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn); } @@ -241,9 +248,9 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { }, [org]); const handleRouting = (): void => { - const showOnboarding = shouldShowOnboarding(); + const showOrgOnboarding = shouldShowOnboarding(); - if (showOnboarding) { + if (showOrgOnboarding && !isOnboardingComplete) { history.push(ROUTES.ONBOARDING); } else { history.push(ROUTES.APPLICATION); @@ -251,17 +258,27 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { }; useEffect(() => { - // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers && isOnboardingComplete !== null) { - const isFirstUser = checkFirstTimeUser(); + const { isPrivate } = currentRoute || { + isPrivate: false, + }; - // Redirect to get started if it's not the first user or if the onboarding is complete - if (isFirstUser && !isOnboardingComplete) { - history.push(ROUTES.ONBOARDING); - } + if (isLoggedInState && role && role !== 'ADMIN') { + setIsLoading(false); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLoadingOrgUsers, isOnboardingComplete, orgUsers]); + + if (!isPrivate) { + setIsLoading(false); + } + + if ( + !isEmpty(user) && + !isFetchingOrgPreferences && + !isEmpty(orgUsers?.payload) && + !isNull(orgPreferences) + ) { + setIsLoading(false); + } + }, [currentRoute, user, role, orgUsers, orgPreferences]); // eslint-disable-next-line sonarjs/cognitive-complexity useEffect(() => { @@ -284,7 +301,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { handlePrivateRoutes(key); } else { // no need to fetch the user and make user fetching false - if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') { handleRouting(); } @@ -311,13 +327,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { history.push(ROUTES.SOMETHING_WENT_WRONG); } })(); - }, [dispatch, isLoggedInState, currentRoute, licensesData]); + }, [ + dispatch, + isLoggedInState, + currentRoute, + licensesData, + orgUsers, + orgPreferences, + ]); if (isUserFetchingError) { return ; } - if (isUserFetching || (isLoggedInState && isFetchingOrgPreferences)) { + if (isUserFetching || isLoading) { return ; } diff --git a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx index c1275ff115..861786f2aa 100644 --- a/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx +++ b/frontend/src/container/OnboardingContainer/OnboardingContainer.tsx @@ -312,7 +312,7 @@ export default function Onboarding(): JSX.Element {
{ logEvent('Onboarding V2: Skip Button Clicked', {}); - history.push('/'); + history.push(ROUTES.APPLICATION); }} className="skip-to-console" > diff --git a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx index 0f99dd315b..fef689de3a 100644 --- a/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx +++ b/frontend/src/container/OnboardingQuestionaire/InviteTeamMembers/InviteTeamMembers.tsx @@ -10,6 +10,7 @@ import { ArrowLeft, ArrowRight, CheckCircle, + Loader2, Plus, TriangleAlert, X, @@ -33,6 +34,7 @@ interface TeamMember { } interface InviteTeamMembersProps { + isLoading: boolean; teamMembers: TeamMember[] | null; setTeamMembers: (teamMembers: TeamMember[]) => void; onNext: () => void; @@ -40,6 +42,7 @@ interface InviteTeamMembersProps { } function InviteTeamMembers({ + isLoading, teamMembers, setTeamMembers, onNext, @@ -67,8 +70,6 @@ function InviteTeamMembers({ const [disableNextButton, setDisableNextButton] = useState(false); - const [allInvitesSent, setAllInvitesSent] = useState(false); - const defaultTeamMember: TeamMember = { email: '', role: 'EDITOR', @@ -157,7 +158,6 @@ function InviteTeamMembers({ setError(null); setHasErrors(false); setInviteUsersErrorResponse(null); - setAllInvitesSent(true); setInviteUsersSuccessResponse(successfulInvites); @@ -358,33 +358,36 @@ function InviteTeamMembers({
)} - {inviteUsersSuccessResponse && ( -
- {inviteUsersSuccessResponse?.map((success, index) => ( - - {success} - - ))} -
- )} - {hasErrors && ( -
- {inviteUsersErrorResponse?.map((error, index) => ( - - {error} - - ))} -
+ <> + {/* show only when invites are sent successfully & partial error is present */} + {inviteUsersSuccessResponse && inviteUsersErrorResponse && ( +
+ {inviteUsersSuccessResponse?.map((success, index) => ( + + {success} + + ))} +
+ )} + +
+ {inviteUsersErrorResponse?.map((error, index) => ( + + {error} + + ))} +
+ )}
@@ -413,17 +416,23 @@ function InviteTeamMembers({ type="primary" className="next-button" onClick={handleNext} - loading={isSendingInvites || disableNextButton} + loading={isSendingInvites || isLoading || disableNextButton} > - {allInvitesSent ? 'Invites Sent' : 'Send Invites'} - - {allInvitesSent ? : } + Send Invites +
-
diff --git a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss index 22cf4c6b2a..784a15bfeb 100644 --- a/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss +++ b/frontend/src/container/OnboardingQuestionaire/OnboardingQuestionaire.styles.scss @@ -189,6 +189,15 @@ justify-content: center; align-items: center; margin-top: 24px; + + .do-later-button { + font-size: 12px; + + display: flex; + justify-content: center; + align-items: center; + gap: 8px; + } } .question { diff --git a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx index 08177c1e27..f1be6fb8ee 100644 --- a/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx +++ b/frontend/src/container/OnboardingQuestionaire/OptimiseSignozNeeds/OptimiseSignozNeeds.tsx @@ -314,7 +314,7 @@ function OptimiseSignozNeeds({
diff --git a/frontend/src/container/OnboardingQuestionaire/index.tsx b/frontend/src/container/OnboardingQuestionaire/index.tsx index 4cb3ecaa80..3b3ed59354 100644 --- a/frontend/src/container/OnboardingQuestionaire/index.tsx +++ b/frontend/src/container/OnboardingQuestionaire/index.tsx @@ -1,31 +1,24 @@ import './OnboardingQuestionaire.styles.scss'; -import { Skeleton } from 'antd'; 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 getOrgPreference from 'api/preferences/getOrgPreference'; import updateOrgPreferenceAPI from 'api/preferences/updateOrgPreference'; -import getOrgUser from 'api/user/getOrgUser'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import ROUTES from 'constants/routes'; import { InviteTeamMembersProps } from 'container/OrganizationSettings/PendingInvitesContainer'; import { useNotifications } from 'hooks/useNotifications'; import history from 'lib/history'; -import { isEmpty } from 'lodash-es'; -import { Dispatch, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { useMutation, useQuery } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; import { UPDATE_IS_FETCHING_ORG_PREFERENCES, UPDATE_ORG_PREFERENCES, } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; -import { USER_ROLES } from 'types/roles'; import { AboutSigNozQuestions, @@ -70,15 +63,14 @@ const INITIAL_OPTIMISE_SIGNOZ_DETAILS: OptimiseSignozDetails = { function OnboardingQuestionaire(): JSX.Element { const { notifications } = useNotifications(); - const { org, role, isLoggedIn: isLoggedInState } = useSelector< - AppState, - AppReducer - >((state) => state.app); + const { org } = useSelector((state) => state.app); + const dispatch = useDispatch(); const [currentStep, setCurrentStep] = useState(1); const [orgDetails, setOrgDetails] = useState(INITIAL_ORG_DETAILS); const [signozDetails, setSignozDetails] = useState( INITIAL_SIGNOZ_DETAILS, ); + const [ optimiseSignozDetails, setOptimiseSignozDetails, @@ -87,113 +79,12 @@ function OnboardingQuestionaire(): JSX.Element { InviteTeamMembersProps[] | null >(null); - const { data: orgUsers, isLoading: isLoadingOrgUsers } = useQuery({ - queryFn: () => - getOrgUser({ - orgId: (org || [])[0].id, - }), - queryKey: ['getOrgUser', org?.[0].id], - }); - - const dispatch = useDispatch>(); const [currentOrgData, setCurrentOrgData] = useState(null); - const [isOnboardingComplete, setIsOnboardingComplete] = useState( - false, - ); - const { - data: onboardingPreferenceData, - isLoading: isLoadingOnboardingPreference, - } = useQuery({ - queryFn: () => getOrgPreference({ preferenceID: 'ORG_ONBOARDING' }), - queryKey: ['getOrgPreferences', 'ORG_ONBOARDING'], - enabled: role === USER_ROLES.ADMIN, - }); - - const { data: orgPreferences, isLoading: isLoadingOrgPreferences } = useQuery({ - queryFn: () => getAllOrgPreferences(), - queryKey: ['getOrgPreferences'], - enabled: isOnboardingComplete && role === USER_ROLES.ADMIN, - }); - - useEffect(() => { - if (orgPreferences && !isLoadingOrgPreferences) { - dispatch({ - type: UPDATE_IS_FETCHING_ORG_PREFERENCES, - payload: { - isFetchingOrgPreferences: false, - }, - }); - - dispatch({ - type: UPDATE_ORG_PREFERENCES, - payload: { - orgPreferences: orgPreferences.payload?.data || null, - }, - }); - } - }, [orgPreferences, dispatch, isLoadingOrgPreferences]); - - useEffect(() => { - if (isLoggedInState && role !== USER_ROLES.ADMIN) { - dispatch({ - type: UPDATE_IS_FETCHING_ORG_PREFERENCES, - payload: { - isFetchingOrgPreferences: false, - }, - }); - } - }, [isLoggedInState, role, dispatch]); - - useEffect(() => { - if ( - !isLoadingOnboardingPreference && - !isEmpty(onboardingPreferenceData?.payload?.data) - ) { - const preferenceId = onboardingPreferenceData?.payload?.data?.preference_id; - const preferenceValue = - onboardingPreferenceData?.payload?.data?.preference_value; - - if (preferenceId === 'ORG_ONBOARDING') { - setIsOnboardingComplete(preferenceValue as boolean); - } - } - }, [onboardingPreferenceData, isLoadingOnboardingPreference]); - - const checkFirstTimeUser = (): boolean => { - const users = orgUsers?.payload || []; - - const remainingUsers = users.filter( - (user) => user.email !== 'admin@signoz.cloud', - ); - - return remainingUsers.length === 1; - }; - - useEffect(() => { - // Only run this effect if the org users and preferences are loaded - if (!isLoadingOrgUsers && !isLoadingOnboardingPreference) { - const isFirstUser = checkFirstTimeUser(); - - // Redirect to get started if it's not the first user or if the onboarding is complete - if (!isFirstUser || isOnboardingComplete) { - history.push(ROUTES.GET_STARTED); - - logEvent('User Onboarding: Redirected to Get Started', { - isFirstUser, - isOnboardingComplete, - }); - } else { - logEvent('User Onboarding: Started', {}); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - isLoadingOrgUsers, - isLoadingOnboardingPreference, - isOnboardingComplete, - orgUsers, - ]); + const [ + updatingOrgOnboardingStatus, + setUpdatingOrgOnboardingStatus, + ] = useState(false); useEffect(() => { if (org) { @@ -207,6 +98,35 @@ function OnboardingQuestionaire(): JSX.Element { // eslint-disable-next-line react-hooks/exhaustive-deps }, [org]); + const { refetch: refetchOrgPreferences } = useQuery({ + queryFn: () => getAllOrgPreferences(), + queryKey: ['getOrgPreferences'], + enabled: false, + refetchOnWindowFocus: false, + onSuccess: (response) => { + dispatch({ + type: UPDATE_IS_FETCHING_ORG_PREFERENCES, + payload: { + isFetchingOrgPreferences: false, + }, + }); + + dispatch({ + type: UPDATE_ORG_PREFERENCES, + payload: { + orgPreferences: response.payload?.data || null, + }, + }); + + setUpdatingOrgOnboardingStatus(false); + + history.push(ROUTES.GET_STARTED); + }, + onError: () => { + setUpdatingOrgOnboardingStatus(false); + }, + }); + const isNextDisabled = optimiseSignozDetails.logsPerDay === 0 && optimiseSignozDetails.hostsPerDay === 0 && @@ -226,10 +146,12 @@ function OnboardingQuestionaire(): JSX.Element { const { mutate: updateOrgPreference } = useMutation(updateOrgPreferenceAPI, { onSuccess: () => { - setIsOnboardingComplete(true); + refetchOrgPreferences(); }, onError: (error) => { showErrorNotification(notifications, error as AxiosError); + + setUpdatingOrgOnboardingStatus(false); }, }); @@ -258,6 +180,7 @@ function OnboardingQuestionaire(): JSX.Element { }; const handleOnboardingComplete = (): void => { + setUpdatingOrgOnboardingStatus(true); updateOrgPreference({ preferenceID: 'ORG_ONBOARDING', value: true, @@ -271,55 +194,46 @@ function OnboardingQuestionaire(): JSX.Element {
- {(isLoadingOnboardingPreference || isLoadingOrgUsers) && ( -
- -
+ {currentStep === 1 && ( + { + setOrgDetails(orgDetails); + setCurrentStep(2); + }} + /> )} - {!isLoadingOnboardingPreference && !isLoadingOrgUsers && ( - <> - {currentStep === 1 && ( - { - setOrgDetails(orgDetails); - setCurrentStep(2); - }} - /> - )} + {currentStep === 2 && ( + setCurrentStep(1)} + onNext={(): void => setCurrentStep(3)} + /> + )} - {currentStep === 2 && ( - setCurrentStep(1)} - onNext={(): void => setCurrentStep(3)} - /> - )} + {currentStep === 3 && ( + 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 + /> + )} - {currentStep === 3 && ( - 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 - /> - )} - - {currentStep === 4 && ( - setCurrentStep(3)} - onNext={handleOnboardingComplete} - /> - )} - + {currentStep === 4 && ( + setCurrentStep(3)} + onNext={handleOnboardingComplete} + /> )}
diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 68f9c19dd1..4917b0fe2d 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -261,7 +261,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { values, async (): Promise => { if (isOnboardingEnabled && isCloudUser()) { - history.push(ROUTES.ONBOARDING); + history.push(ROUTES.GET_STARTED); } else { history.push(ROUTES.APPLICATION); } From db4338be423184b759329f95ac28a0d5cb04c324 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sat, 2 Nov 2024 01:23:43 +0530 Subject: [PATCH 32/52] chore: add feature flag, handle out-of-index error, some house keeping work (#6344) --- ee/query-service/model/plans.go | 22 + pkg/query-service/app/inframetrics/common.go | 4 + pkg/query-service/app/inframetrics/hosts.go | 413 ++++++++---------- .../{table_list_query.go => hosts_query.go} | 171 ++------ .../app/inframetrics/namespaces.go | 4 +- pkg/query-service/app/inframetrics/pods.go | 4 +- .../app/inframetrics/process_query.go | 73 ++++ .../app/inframetrics/processes.go | 329 +++++++------- .../app/inframetrics/table_list_query_k8s.go | 269 ------------ pkg/query-service/constants/constants.go | 4 + pkg/query-service/model/featureSet.go | 1 + pkg/query-service/model/infra.go | 60 +-- 12 files changed, 517 insertions(+), 837 deletions(-) rename pkg/query-service/app/inframetrics/{table_list_query.go => hosts_query.go} (67%) create mode 100644 pkg/query-service/app/inframetrics/process_query.go delete mode 100644 pkg/query-service/app/inframetrics/table_list_query_k8s.go diff --git a/ee/query-service/model/plans.go b/ee/query-service/model/plans.go index 5b695143b7..c5272340a3 100644 --- a/ee/query-service/model/plans.go +++ b/ee/query-service/model/plans.go @@ -1,6 +1,7 @@ package model import ( + "go.signoz.io/signoz/pkg/query-service/constants" basemodel "go.signoz.io/signoz/pkg/query-service/model" ) @@ -134,6 +135,13 @@ var BasicPlan = basemodel.FeatureSet{ UsageLimit: -1, Route: "", }, + basemodel.Feature{ + Name: basemodel.HostsInfraMonitoring, + Active: constants.EnableHostsInfraMonitoring(), + Usage: 0, + UsageLimit: -1, + Route: "", + }, } var ProPlan = basemodel.FeatureSet{ @@ -249,6 +257,13 @@ var ProPlan = basemodel.FeatureSet{ UsageLimit: -1, Route: "", }, + basemodel.Feature{ + Name: basemodel.HostsInfraMonitoring, + Active: constants.EnableHostsInfraMonitoring(), + Usage: 0, + UsageLimit: -1, + Route: "", + }, } var EnterprisePlan = basemodel.FeatureSet{ @@ -378,4 +393,11 @@ var EnterprisePlan = basemodel.FeatureSet{ UsageLimit: -1, Route: "", }, + basemodel.Feature{ + Name: basemodel.HostsInfraMonitoring, + Active: constants.EnableHostsInfraMonitoring(), + Usage: 0, + UsageLimit: -1, + Route: "", + }, } diff --git a/pkg/query-service/app/inframetrics/common.go b/pkg/query-service/app/inframetrics/common.go index 6f83a6d46c..7cde41185e 100644 --- a/pkg/query-service/app/inframetrics/common.go +++ b/pkg/query-service/app/inframetrics/common.go @@ -53,6 +53,10 @@ func getParamsForTopHosts(req model.HostListRequest) (int64, string, string) { return getParamsForTopItems(req.Start, req.End) } +func getParamsForTopProcesses(req model.ProcessListRequest) (int64, string, string) { + return getParamsForTopItems(req.Start, req.End) +} + func getParamsForTopPods(req model.PodListRequest) (int64, string, string) { return getParamsForTopItems(req.Start, req.End) } diff --git a/pkg/query-service/app/inframetrics/hosts.go b/pkg/query-service/app/inframetrics/hosts.go index b41cf1ecc1..8aae039d3e 100644 --- a/pkg/query-service/app/inframetrics/hosts.go +++ b/pkg/query-service/app/inframetrics/hosts.go @@ -2,10 +2,12 @@ package inframetrics import ( "context" + "math" "sort" "strings" "time" + "go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers" "go.signoz.io/signoz/pkg/query-service/common" "go.signoz.io/signoz/pkg/query-service/interfaces" "go.signoz.io/signoz/pkg/query-service/model" @@ -54,9 +56,16 @@ var ( // TODO(srikanthccv): remove hardcoded metric name and support keys from any system metric metricToUseForHostAttributes = "system_cpu_load_average_15m" hostNameAttrKey = "host_name" - // TODO(srikanthccv): remove k8s hacky logic from hosts repo after charts users are migrated - k8sNodeNameAttrKey = "k8s_node_name" - agentNameToIgnore = "k8s-infra-otel-agent" + agentNameToIgnore = "k8s-infra-otel-agent" + hostAttrsToEnrich = []string{ + "os_type", + } + metricNamesForHosts = map[string]string{ + "cpu": "system_cpu_time", + "memory": "system_memory_usage", + "load15": "system_cpu_load_average_15m", + "wait": "system_cpu_time", + } ) func NewHostsRepo(reader interfaces.Reader, querierV2 interfaces.Querier) *HostsRepo { @@ -112,29 +121,10 @@ func (h *HostsRepo) GetHostAttributeValues(ctx context.Context, req v3.FilterAtt hostNames = append(hostNames, attributeValue) } - req.FilterAttributeKey = k8sNodeNameAttrKey - req.DataSource = v3.DataSourceMetrics - req.AggregateAttribute = metricToUseForHostAttributes - if req.Limit == 0 { - req.Limit = 50 - } - - attributeValuesResponse, err = h.reader.GetMetricAttributeValues(ctx, &req) - if err != nil { - return nil, err - } - for _, attributeValue := range attributeValuesResponse.StringAttributeValues { - if strings.Contains(attributeValue, agentNameToIgnore) { - continue - } - hostNames = append(hostNames, attributeValue) - } - return &v3.FilterAttributeValueResponse{StringAttributeValues: hostNames}, nil } -func (h *HostsRepo) getActiveHosts(ctx context.Context, - req model.HostListRequest, hostNameAttrKey string) (map[string]bool, error) { +func (h *HostsRepo) getActiveHosts(ctx context.Context, req model.HostListRequest) (map[string]bool, error) { activeStatus := map[string]bool{} step := common.MinAllowedStepInterval(req.Start, req.End) @@ -192,12 +182,72 @@ func (h *HostsRepo) getActiveHosts(ctx context.Context, return activeStatus, nil } -// getTopHosts returns the top hosts for the given order by column name -func (h *HostsRepo) getTopHosts(ctx context.Context, req model.HostListRequest, q *v3.QueryRangeParamsV3, hostNameAttrKey string) ([]string, []string, error) { +func (h *HostsRepo) getMetadataAttributes(ctx context.Context, req model.HostListRequest) (map[string]map[string]string, error) { + hostAttrs := map[string]map[string]string{} + + for _, key := range hostAttrsToEnrich { + hasKey := false + for _, groupByKey := range req.GroupBy { + if groupByKey.Key == key { + hasKey = true + break + } + } + if !hasKey { + req.GroupBy = append(req.GroupBy, v3.AttributeKey{Key: key}) + } + } + + mq := v3.BuilderQuery{ + DataSource: v3.DataSourceMetrics, + AggregateAttribute: v3.AttributeKey{ + Key: metricToUseForHostAttributes, + DataType: v3.AttributeKeyDataTypeFloat64, + }, + Temporality: v3.Unspecified, + GroupBy: req.GroupBy, + } + + query, err := helpers.PrepareTimeseriesFilterQuery(req.Start, req.End, &mq) + if err != nil { + return nil, err + } + + query = localQueryToDistributedQuery(query) + + attrsListResponse, err := h.reader.GetListResultV3(ctx, query) + if err != nil { + return nil, err + } + + for _, row := range attrsListResponse { + stringData := map[string]string{} + for key, value := range row.Data { + if str, ok := value.(string); ok { + stringData[key] = str + } else if strPtr, ok := value.(*string); ok { + stringData[key] = *strPtr + } + } + + hostName := stringData[hostNameAttrKey] + if _, ok := hostAttrs[hostName]; !ok { + hostAttrs[hostName] = map[string]string{} + } + + for _, key := range req.GroupBy { + hostAttrs[hostName][key.Key] = stringData[key.Key] + } + } + + return hostAttrs, nil +} + +func (h *HostsRepo) getTopHostGroups(ctx context.Context, req model.HostListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) { step, timeSeriesTableName, samplesTableName := getParamsForTopHosts(req) queryNames := queryNamesForTopHosts[req.OrderBy.ColumnName] - topHostsQueryRangeParams := &v3.QueryRangeParamsV3{ + topHostGroupsQueryRangeParams := &v3.QueryRangeParamsV3{ Start: req.Start, End: req.End, Step: step, @@ -216,19 +266,16 @@ func (h *HostsRepo) getTopHosts(ctx context.Context, req model.HostListRequest, SamplesTableName: samplesTableName, } if req.Filters != nil && len(req.Filters.Items) > 0 { - if query.Filters == nil { - query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}} - } query.Filters.Items = append(query.Filters.Items, req.Filters.Items...) } - topHostsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query + topHostGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query } - queryResponse, _, err := h.querierV2.QueryRange(ctx, topHostsQueryRangeParams) + queryResponse, _, err := h.querierV2.QueryRange(ctx, topHostGroupsQueryRangeParams) if err != nil { return nil, nil, err } - formattedResponse, err := postprocess.PostProcessResult(queryResponse, topHostsQueryRangeParams) + formattedResponse, err := postprocess.PostProcessResult(queryResponse, topHostGroupsQueryRangeParams) if err != nil { return nil, nil, err } @@ -247,238 +294,150 @@ func (h *HostsRepo) getTopHosts(ctx context.Context, req model.HostListRequest, }) } - paginatedTopHostsSeries := formattedResponse[0].Series[req.Offset : req.Offset+req.Limit] + limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series))) - topHosts := []string{} - for _, series := range paginatedTopHostsSeries { - topHosts = append(topHosts, series.Labels[hostNameAttrKey]) + paginatedTopHostGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)] + + topHostGroups := []map[string]string{} + for _, series := range paginatedTopHostGroupsSeries { + topHostGroups = append(topHostGroups, series.Labels) } - allHosts := []string{} + allHostGroups := []map[string]string{} for _, series := range formattedResponse[0].Series { - allHosts = append(allHosts, series.Labels[hostNameAttrKey]) + allHostGroups = append(allHostGroups, series.Labels) } - return topHosts, allHosts, nil + return topHostGroups, allHostGroups, nil } -func (h *HostsRepo) getHostsForQuery(ctx context.Context, - req model.HostListRequest, q *v3.QueryRangeParamsV3, hostNameAttrKey string) ([]model.HostListRecord, []string, error) { +func (h *HostsRepo) GetHostList(ctx context.Context, req model.HostListRequest) (model.HostListResponse, error) { + resp := model.HostListResponse{} - step := common.MinAllowedStepInterval(req.Start, req.End) + if req.Limit == 0 { + req.Limit = 10 + } - query := q.Clone() + // default to cpu order by + if req.OrderBy == nil { + req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc} + } + + // default to host name group by + if len(req.GroupBy) == 0 { + req.GroupBy = []v3.AttributeKey{{Key: hostNameAttrKey}} + resp.Type = model.ResponseTypeList + } else { + resp.Type = model.ResponseTypeGroupedList + } + + step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60)) + + query := HostsTableListQuery.Clone() query.Start = req.Start query.End = req.End query.Step = step - topHosts, allHosts, err := h.getTopHosts(ctx, req, q, hostNameAttrKey) - if err != nil { - return nil, nil, err - } - for _, query := range query.CompositeQuery.BuilderQueries { query.StepInterval = step - // check if the filter has host_name and is either IN or EQUAL operator - // if so, we don't need to add the topHosts filter again - hasHostNameInOrEqual := false - if req.Filters != nil && len(req.Filters.Items) > 0 { - for _, item := range req.Filters.Items { - if item.Key.Key == hostNameAttrKey && (item.Operator == v3.FilterOperatorIn || item.Operator == v3.FilterOperatorEqual) { - hasHostNameInOrEqual = true - } - } - if query.Filters == nil { - query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}} - } query.Filters.Items = append(query.Filters.Items, req.Filters.Items...) - // what is happening here? - // if the filter has host_name and we are querying for k8s host metrics, - // we need to replace the host_name with k8s_node_name - if hostNameAttrKey == k8sNodeNameAttrKey { - for idx, item := range query.Filters.Items { - if item.Key.Key == hostNameAttrKey { - query.Filters.Items[idx].Key.Key = k8sNodeNameAttrKey - } - } - } } - if !hasHostNameInOrEqual { - if query.Filters == nil { - query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}} - } - query.Filters.Items = append(query.Filters.Items, v3.FilterItem{ - Key: v3.AttributeKey{ - Key: hostNameAttrKey, - }, - Value: topHosts, - Operator: v3.FilterOperatorIn, - }) + query.GroupBy = req.GroupBy + } + + hostAttrs, err := h.getMetadataAttributes(ctx, req) + if err != nil { + return resp, err + } + + activeHosts, err := h.getActiveHosts(ctx, req) + if err != nil { + return resp, err + } + + topHostGroups, allHostGroups, err := h.getTopHostGroups(ctx, req, query) + if err != nil { + return resp, err + } + + groupFilters := map[string][]string{} + for _, topHostGroup := range topHostGroups { + for k, v := range topHostGroup { + groupFilters[k] = append(groupFilters[k], v) } } - activeHosts, err := h.getActiveHosts(ctx, req, hostNameAttrKey) - if err != nil { - return nil, nil, err + for groupKey, groupValues := range groupFilters { + hasGroupFilter := false + if req.Filters != nil && len(req.Filters.Items) > 0 { + for _, filter := range req.Filters.Items { + if filter.Key.Key == groupKey { + hasGroupFilter = true + break + } + } + } + if !hasGroupFilter { + for _, query := range query.CompositeQuery.BuilderQueries { + query.Filters.Items = append(query.Filters.Items, v3.FilterItem{ + Key: v3.AttributeKey{Key: groupKey}, + Value: groupValues, + Operator: v3.FilterOperatorIn, + }) + } + } } queryResponse, _, err := h.querierV2.QueryRange(ctx, query) if err != nil { - return nil, nil, err + return resp, err } - type hostTSInfo struct { - cpuTimeSeries *v3.Series - memoryTimeSeries *v3.Series - waitTimeSeries *v3.Series - load15TimeSeries *v3.Series - } - hostTSInfoMap := map[string]*hostTSInfo{} - - for _, result := range queryResponse { - for _, series := range result.Series { - hostName := series.Labels[hostNameAttrKey] - if _, ok := hostTSInfoMap[hostName]; !ok { - hostTSInfoMap[hostName] = &hostTSInfo{} - } - if result.QueryName == "G" { - loadSeries := *series - hostTSInfoMap[hostName].load15TimeSeries = &loadSeries - } - } - } - - query.FormatForWeb = false - query.CompositeQuery.PanelType = v3.PanelTypeGraph - - formulaResult, err := postprocess.PostProcessResult(queryResponse, query) + formattedResponse, err := postprocess.PostProcessResult(queryResponse, query) if err != nil { - return nil, nil, err + return resp, err } - for _, result := range formulaResult { - for _, series := range result.Series { - hostName := series.Labels[hostNameAttrKey] - if _, ok := hostTSInfoMap[hostName]; !ok { - hostTSInfoMap[hostName] = &hostTSInfo{} - } - if result.QueryName == "F1" { - hostTSInfoMap[hostName].cpuTimeSeries = series - } else if result.QueryName == "F2" { - hostTSInfoMap[hostName].memoryTimeSeries = series - } else if result.QueryName == "F3" { - hostTSInfoMap[hostName].waitTimeSeries = series - } - } - } - - query.FormatForWeb = true - query.CompositeQuery.PanelType = v3.PanelTypeTable - formattedResponse, _ := postprocess.PostProcessResult(queryResponse, query) - records := []model.HostListRecord{} - // there should be only one result in the response - hostsInfo := formattedResponse[0] - // each row represents a host - for _, row := range hostsInfo.Table.Rows { - record := model.HostListRecord{ - CPU: -1, - Memory: -1, - Wait: -1, - Load15: -1, - } + for _, result := range formattedResponse { + for _, row := range result.Table.Rows { + record := model.HostListRecord{ + CPU: -1, + Memory: -1, + Wait: -1, + Load15: -1, + } - hostName, ok := row.Data[hostNameAttrKey].(string) - if ok { - record.HostName = hostName - } + if hostName, ok := row.Data[hostNameAttrKey].(string); ok { + record.HostName = hostName + } - osType, ok := row.Data["os_type"].(string) - if ok { - record.OS = osType - } - - cpu, ok := row.Data["F1"].(float64) - if ok { - record.CPU = cpu - } - memory, ok := row.Data["F2"].(float64) - if ok { - record.Memory = memory - } - wait, ok := row.Data["F3"].(float64) - if ok { - record.Wait = wait - } - load15, ok := row.Data["G"].(float64) - if ok { - record.Load15 = load15 - } - record.Active = activeHosts[record.HostName] - if hostTSInfoMap[record.HostName] != nil { - record.CPUTimeSeries = hostTSInfoMap[record.HostName].cpuTimeSeries - record.MemoryTimeSeries = hostTSInfoMap[record.HostName].memoryTimeSeries - record.WaitTimeSeries = hostTSInfoMap[record.HostName].waitTimeSeries - record.Load15TimeSeries = hostTSInfoMap[record.HostName].load15TimeSeries - } - records = append(records, record) - } - - return records, allHosts, nil -} - -func dedupRecords(records []model.HostListRecord) []model.HostListRecord { - seen := map[string]bool{} - deduped := []model.HostListRecord{} - for _, record := range records { - if !seen[record.HostName] { - seen[record.HostName] = true - deduped = append(deduped, record) + if cpu, ok := row.Data["F1"].(float64); ok { + record.CPU = cpu + } + if memory, ok := row.Data["F2"].(float64); ok { + record.Memory = memory + } + if wait, ok := row.Data["F3"].(float64); ok { + record.Wait = wait + } + if load15, ok := row.Data["G"].(float64); ok { + record.Load15 = load15 + } + record.Meta = map[string]string{} + if _, ok := hostAttrs[record.HostName]; ok { + record.Meta = hostAttrs[record.HostName] + } + if osType, ok := record.Meta["os_type"]; ok { + record.OS = osType + } + record.Active = activeHosts[record.HostName] + records = append(records, record) } } - return deduped -} - -func (h *HostsRepo) GetHostList(ctx context.Context, req model.HostListRequest) (model.HostListResponse, error) { - if req.Limit == 0 { - req.Limit = 10 - } - - if req.OrderBy == nil { - req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc} - } - - resp := model.HostListResponse{ - Type: "list", - } - - vmRecords, vmAllHosts, err := h.getHostsForQuery(ctx, req, &NonK8STableListQuery, hostNameAttrKey) - if err != nil { - return resp, err - } - k8sRecords, k8sAllHosts, err := h.getHostsForQuery(ctx, req, &K8STableListQuery, k8sNodeNameAttrKey) - if err != nil { - return resp, err - } - - uniqueHosts := map[string]bool{} - for _, host := range vmAllHosts { - uniqueHosts[host] = true - } - for _, host := range k8sAllHosts { - uniqueHosts[host] = true - } - - records := append(vmRecords, k8sRecords...) - - // since we added the fix for incorrect host name, it is possible that both host_name and k8s_node_name - // are present in the response. we need to dedup the results. - records = dedupRecords(records) - - resp.Total = len(uniqueHosts) - + resp.Total = len(allHostGroups) resp.Records = records return resp, nil diff --git a/pkg/query-service/app/inframetrics/table_list_query.go b/pkg/query-service/app/inframetrics/hosts_query.go similarity index 67% rename from pkg/query-service/app/inframetrics/table_list_query.go rename to pkg/query-service/app/inframetrics/hosts_query.go index 3ea748a354..dff7963127 100644 --- a/pkg/query-service/app/inframetrics/table_list_query.go +++ b/pkg/query-service/app/inframetrics/hosts_query.go @@ -2,14 +2,14 @@ package inframetrics import v3 "go.signoz.io/signoz/pkg/query-service/model/v3" -var NonK8STableListQuery = v3.QueryRangeParamsV3{ +var HostsTableListQuery = v3.QueryRangeParamsV3{ CompositeQuery: &v3.CompositeQuery{ BuilderQueries: map[string]*v3.BuilderQuery{ "A": { QueryName: "A", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", + Key: metricNamesForHosts["cpu"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -27,23 +27,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ }, { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -58,7 +53,7 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "B", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", + Key: metricNamesForHosts["cpu"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -67,23 +62,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ Items: []v3.FilterItem{ { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -98,12 +88,16 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "F1", Expression: "A/B", Legend: "CPU Usage (%)", + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, }, "C": { QueryName: "C", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_memory_usage", + Key: metricNamesForHosts["memory"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -121,23 +115,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ }, { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -152,7 +141,7 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "D", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_memory_usage", + Key: metricNamesForHosts["memory"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -161,23 +150,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ Items: []v3.FilterItem{ { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -192,12 +176,16 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "F2", Expression: "C/D", Legend: "Memory Usage (%)", + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, }, "E": { QueryName: "E", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", + Key: metricNamesForHosts["wait"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -215,23 +203,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ }, { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -246,7 +229,7 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "F", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", + Key: metricNamesForHosts["wait"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -255,23 +238,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ Items: []v3.FilterItem{ { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -286,12 +264,16 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ QueryName: "F3", Expression: "E/F", Legend: "CPU Wait Time (%)", + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, }, "G": { QueryName: "G", DataSource: v3.DataSourceMetrics, AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_load_average_15m", + Key: metricNamesForHosts["load15"], DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Unspecified, @@ -300,23 +282,18 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ Items: []v3.FilterItem{ { Key: v3.AttributeKey{ - Key: "host_name", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, Operator: v3.FilterOperatorNotContains, - Value: "k8s-infra-otel-agent", + Value: agentNameToIgnore, }, }, }, GroupBy: []v3.AttributeKey{ { - Key: "host_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", + Key: hostNameAttrKey, DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeResource, }, @@ -335,69 +312,3 @@ var NonK8STableListQuery = v3.QueryRangeParamsV3{ Version: "v4", FormatForWeb: true, } - -var ProcessesTableListQuery = v3.QueryRangeParamsV3{ - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "process_cpu_time", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "process_pid", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "A", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationRate, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "F1": { - QueryName: "F1", - Expression: "A", - Legend: "Process CPU Usage (%)", - }, - "C": { - QueryName: "C", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "process_memory_usage", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "process_pid", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "C", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationAvg, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: false, - }, - }, - PanelType: v3.PanelTypeTable, - QueryType: v3.QueryTypeBuilder, - }, - Version: "v4", - FormatForWeb: true, -} diff --git a/pkg/query-service/app/inframetrics/namespaces.go b/pkg/query-service/app/inframetrics/namespaces.go index 3e336672c5..c1e5928e16 100644 --- a/pkg/query-service/app/inframetrics/namespaces.go +++ b/pkg/query-service/app/inframetrics/namespaces.go @@ -178,7 +178,9 @@ func (p *NamespacesRepo) getTopNamespaceGroups(ctx context.Context, req model.Na }) } - paginatedTopNamespaceGroupsSeries := formattedResponse[0].Series[req.Offset : req.Offset+req.Limit] + limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series))) + + paginatedTopNamespaceGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)] topNamespaceGroups := []map[string]string{} for _, series := range paginatedTopNamespaceGroupsSeries { diff --git a/pkg/query-service/app/inframetrics/pods.go b/pkg/query-service/app/inframetrics/pods.go index 2bf101f746..908aef10c1 100644 --- a/pkg/query-service/app/inframetrics/pods.go +++ b/pkg/query-service/app/inframetrics/pods.go @@ -217,7 +217,9 @@ func (p *PodsRepo) getTopPodGroups(ctx context.Context, req model.PodListRequest }) } - paginatedTopPodGroupsSeries := formattedResponse[0].Series[req.Offset : req.Offset+req.Limit] + limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series))) + + paginatedTopPodGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)] topPodGroups := []map[string]string{} for _, series := range paginatedTopPodGroupsSeries { diff --git a/pkg/query-service/app/inframetrics/process_query.go b/pkg/query-service/app/inframetrics/process_query.go new file mode 100644 index 0000000000..88a329cba5 --- /dev/null +++ b/pkg/query-service/app/inframetrics/process_query.go @@ -0,0 +1,73 @@ +package inframetrics + +import v3 "go.signoz.io/signoz/pkg/query-service/model/v3" + +var ProcessesTableListQuery = v3.QueryRangeParamsV3{ + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + DataSource: v3.DataSourceMetrics, + AggregateAttribute: v3.AttributeKey{ + Key: metricNamesForProcesses["cpu"], + DataType: v3.AttributeKeyDataTypeFloat64, + }, + Temporality: v3.Cumulative, + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, + GroupBy: []v3.AttributeKey{ + { + Key: processPIDAttrKey, + DataType: v3.AttributeKeyDataTypeString, + Type: v3.AttributeKeyTypeResource, + }, + }, + Expression: "A", + ReduceTo: v3.ReduceToOperatorAvg, + TimeAggregation: v3.TimeAggregationRate, + SpaceAggregation: v3.SpaceAggregationSum, + Disabled: true, + }, + "F1": { + QueryName: "F1", + Expression: "A", + Legend: "Process CPU Usage (%)", + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, + }, + "C": { + QueryName: "C", + DataSource: v3.DataSourceMetrics, + AggregateAttribute: v3.AttributeKey{ + Key: metricNamesForProcesses["memory"], + DataType: v3.AttributeKeyDataTypeFloat64, + }, + Temporality: v3.Cumulative, + Filters: &v3.FilterSet{ + Operator: "AND", + Items: []v3.FilterItem{}, + }, + GroupBy: []v3.AttributeKey{ + { + Key: processPIDAttrKey, + DataType: v3.AttributeKeyDataTypeString, + Type: v3.AttributeKeyTypeResource, + }, + }, + Expression: "C", + ReduceTo: v3.ReduceToOperatorAvg, + TimeAggregation: v3.TimeAggregationAvg, + SpaceAggregation: v3.SpaceAggregationSum, + Disabled: false, + }, + }, + PanelType: v3.PanelTypeTable, + QueryType: v3.QueryTypeBuilder, + }, + Version: "v4", + FormatForWeb: true, +} diff --git a/pkg/query-service/app/inframetrics/processes.go b/pkg/query-service/app/inframetrics/processes.go index 5ca3c74c21..2f98efdcfc 100644 --- a/pkg/query-service/app/inframetrics/processes.go +++ b/pkg/query-service/app/inframetrics/processes.go @@ -2,9 +2,8 @@ package inframetrics import ( "context" - "fmt" "math" - "strings" + "sort" "go.signoz.io/signoz/pkg/query-service/app/metrics/v4/helpers" "go.signoz.io/signoz/pkg/query-service/common" @@ -15,6 +14,23 @@ import ( "golang.org/x/exp/slices" ) +var ( + queryNamesForTopProcesses = map[string][]string{ + "cpu": {"A"}, + "memory": {"C"}, + } + + processPIDAttrKey = "process_pid" + metricNamesForProcesses = map[string]string{ + "cpu": "process_cpu_time", + "memory": "process_memory_usage", + } + metricToUseForProcessAttributes = "process_memory_usage" + processNameAttrKey = "process_executable_name" + processCMDAttrKey = "process_command" + processCMDLineAttrKey = "process_command_line" +) + type ProcessesRepo struct { reader interfaces.Reader querierV2 interfaces.Querier @@ -64,14 +80,6 @@ func (p *ProcessesRepo) GetProcessAttributeValues(ctx context.Context, req v3.Fi return attributeValuesResponse, nil } -func getGroupKeyForProcesses(record model.ProcessListRecord, groupBy []v3.AttributeKey) string { - groupKey := "" - for _, key := range groupBy { - groupKey += fmt.Sprintf("%s=%s,", key.Key, record.Meta[key.Key]) - } - return groupKey -} - func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context, req model.ProcessListRequest) (map[string]map[string]string, error) { processAttrs := map[string]map[string]string{} @@ -92,7 +100,7 @@ func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context, mq := v3.BuilderQuery{ AggregateAttribute: v3.AttributeKey{ - Key: "process_memory_usage", + Key: metricToUseForProcessAttributes, DataType: v3.AttributeKeyDataTypeFloat64, }, Temporality: v3.Cumulative, @@ -104,14 +112,7 @@ func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context, return nil, err } - // TODO(srikanthccv): remove this - // What is happening here? - // The `PrepareTimeseriesFilterQuery` uses the local time series table for sub-query because each fingerprint - // goes to same shard. - // However, in this case, we are interested in the attributes values across all the shards. - // So, we replace the local time series table with the distributed time series table. - // See `PrepareTimeseriesFilterQuery` for more details. - query = strings.Replace(query, ".time_series_v4", ".distributed_time_series_v4", 1) + query = localQueryToDistributedQuery(query) attrsListResponse, err := p.reader.GetListResultV3(ctx, query) if err != nil { @@ -128,36 +129,108 @@ func (p *ProcessesRepo) getMetadataAttributes(ctx context.Context, } } - pid := stringData["process_pid"] - if _, ok := processAttrs[pid]; !ok { - processAttrs[pid] = map[string]string{} + processID := stringData[processPIDAttrKey] + if _, ok := processAttrs[processID]; !ok { + processAttrs[processID] = map[string]string{} } for _, key := range req.GroupBy { - processAttrs[pid][key.Key] = stringData[key.Key] + processAttrs[processID][key.Key] = stringData[key.Key] } } return processAttrs, nil } +func (p *ProcessesRepo) getTopProcessGroups(ctx context.Context, req model.ProcessListRequest, q *v3.QueryRangeParamsV3) ([]map[string]string, []map[string]string, error) { + step, timeSeriesTableName, samplesTableName := getParamsForTopProcesses(req) + + queryNames := queryNamesForTopProcesses[req.OrderBy.ColumnName] + topProcessGroupsQueryRangeParams := &v3.QueryRangeParamsV3{ + Start: req.Start, + End: req.End, + Step: step, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{}, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeTable, + }, + } + + for _, queryName := range queryNames { + query := q.CompositeQuery.BuilderQueries[queryName].Clone() + query.StepInterval = step + query.MetricTableHints = &v3.MetricTableHints{ + TimeSeriesTableName: timeSeriesTableName, + SamplesTableName: samplesTableName, + } + if req.Filters != nil && len(req.Filters.Items) > 0 { + query.Filters.Items = append(query.Filters.Items, req.Filters.Items...) + } + topProcessGroupsQueryRangeParams.CompositeQuery.BuilderQueries[queryName] = query + } + + queryResponse, _, err := p.querierV2.QueryRange(ctx, topProcessGroupsQueryRangeParams) + if err != nil { + return nil, nil, err + } + formattedResponse, err := postprocess.PostProcessResult(queryResponse, topProcessGroupsQueryRangeParams) + if err != nil { + return nil, nil, err + } + + if len(formattedResponse) == 0 || len(formattedResponse[0].Series) == 0 { + return nil, nil, nil + } + + if req.OrderBy.Order == v3.DirectionDesc { + sort.Slice(formattedResponse[0].Series, func(i, j int) bool { + return formattedResponse[0].Series[i].Points[0].Value > formattedResponse[0].Series[j].Points[0].Value + }) + } else { + sort.Slice(formattedResponse[0].Series, func(i, j int) bool { + return formattedResponse[0].Series[i].Points[0].Value < formattedResponse[0].Series[j].Points[0].Value + }) + } + + limit := math.Min(float64(req.Offset+req.Limit), float64(len(formattedResponse[0].Series))) + + paginatedTopProcessGroupsSeries := formattedResponse[0].Series[req.Offset:int(limit)] + + topProcessGroups := []map[string]string{} + for _, series := range paginatedTopProcessGroupsSeries { + topProcessGroups = append(topProcessGroups, series.Labels) + } + allProcessGroups := []map[string]string{} + for _, series := range formattedResponse[0].Series { + allProcessGroups = append(allProcessGroups, series.Labels) + } + + return topProcessGroups, allProcessGroups, nil +} + func (p *ProcessesRepo) GetProcessList(ctx context.Context, req model.ProcessListRequest) (model.ProcessListResponse, error) { + resp := model.ProcessListResponse{} if req.Limit == 0 { req.Limit = 10 } - resp := model.ProcessListResponse{ - Type: "list", + // default to cpu order by + if req.OrderBy == nil { + req.OrderBy = &v3.OrderBy{ColumnName: "cpu", Order: v3.DirectionDesc} } - step := common.MinAllowedStepInterval(req.Start, req.End) + // default to process pid group by + if len(req.GroupBy) == 0 { + req.GroupBy = []v3.AttributeKey{{Key: processPIDAttrKey}} + resp.Type = model.ResponseTypeList + } else { + resp.Type = model.ResponseTypeGroupedList + } + + step := int64(math.Max(float64(common.MinAllowedStepInterval(req.Start, req.End)), 60)) query := ProcessesTableListQuery.Clone() - if req.OrderBy != nil { - for _, q := range query.CompositeQuery.BuilderQueries { - q.OrderBy = []v3.OrderBy{*req.OrderBy} - } - } query.Start = req.Start query.End = req.End @@ -166,11 +239,9 @@ func (p *ProcessesRepo) GetProcessList(ctx context.Context, req model.ProcessLis for _, query := range query.CompositeQuery.BuilderQueries { query.StepInterval = step if req.Filters != nil && len(req.Filters.Items) > 0 { - if query.Filters == nil { - query.Filters = &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}} - } query.Filters.Items = append(query.Filters.Items, req.Filters.Items...) } + query.GroupBy = req.GroupBy } processAttrs, err := p.getMetadataAttributes(ctx, req) @@ -178,157 +249,83 @@ func (p *ProcessesRepo) GetProcessList(ctx context.Context, req model.ProcessLis return resp, err } + topProcessGroups, allProcessGroups, err := p.getTopProcessGroups(ctx, req, query) + if err != nil { + return resp, err + } + + groupFilters := map[string][]string{} + for _, topProcessGroup := range topProcessGroups { + for k, v := range topProcessGroup { + groupFilters[k] = append(groupFilters[k], v) + } + } + + for groupKey, groupValues := range groupFilters { + hasGroupFilter := false + if req.Filters != nil && len(req.Filters.Items) > 0 { + for _, filter := range req.Filters.Items { + if filter.Key.Key == groupKey { + hasGroupFilter = true + break + } + } + } + + if !hasGroupFilter { + for _, query := range query.CompositeQuery.BuilderQueries { + query.Filters.Items = append(query.Filters.Items, v3.FilterItem{ + Key: v3.AttributeKey{Key: groupKey}, + Value: groupValues, + Operator: v3.FilterOperatorIn, + }) + } + } + } + queryResponse, _, err := p.querierV2.QueryRange(ctx, query) if err != nil { return resp, err } - type processTSInfo struct { - CpuTimeSeries *v3.Series `json:"cpu_time_series"` - MemoryTimeSeries *v3.Series `json:"memory_time_series"` - } - processTSInfoMap := map[string]*processTSInfo{} - - for _, result := range queryResponse { - for _, series := range result.Series { - pid := series.Labels["process_pid"] - if _, ok := processTSInfoMap[pid]; !ok { - processTSInfoMap[pid] = &processTSInfo{} - } - } - } - - query.FormatForWeb = false - query.CompositeQuery.PanelType = v3.PanelTypeGraph - - formulaResult, err := postprocess.PostProcessResult(queryResponse, query) - if err != nil { - return resp, err - } - - for _, result := range formulaResult { - for _, series := range result.Series { - pid := series.Labels["process_pid"] - if _, ok := processTSInfoMap[pid]; !ok { - processTSInfoMap[pid] = &processTSInfo{} - } - loadSeries := *series - if result.QueryName == "F1" { - processTSInfoMap[pid].CpuTimeSeries = &loadSeries - } else if result.QueryName == "C" { - processTSInfoMap[pid].MemoryTimeSeries = &loadSeries - } - } - } - - query.FormatForWeb = true - query.CompositeQuery.PanelType = v3.PanelTypeTable - formattedResponse, err := postprocess.PostProcessResult(queryResponse, query) if err != nil { return resp, err } - if len(formattedResponse) == 0 { - return resp, nil - } - records := []model.ProcessListRecord{} - // there should be only one result in the response - processInfo := formattedResponse[0] + for _, result := range formattedResponse { + for _, row := range result.Table.Rows { + record := model.ProcessListRecord{ + ProcessCPU: -1, + ProcessMemory: -1, + } - for _, row := range processInfo.Table.Rows { - record := model.ProcessListRecord{ - ProcessCPU: -1, - ProcessMemory: -1, - } + pid, ok := row.Data[processPIDAttrKey].(string) + if ok { + record.ProcessID = pid + } - pid, ok := row.Data["process_pid"].(string) - if ok { - record.ProcessID = pid - } + processCPU, ok := row.Data["F1"].(float64) + if ok { + record.ProcessCPU = processCPU + } - processCPU, ok := row.Data["F1"].(float64) - if ok { - record.ProcessCPU = processCPU + processMemory, ok := row.Data["C"].(float64) + if ok { + record.ProcessMemory = processMemory + } + record.Meta = processAttrs[record.ProcessID] + record.ProcessName = record.Meta[processNameAttrKey] + record.ProcessCMD = record.Meta[processCMDAttrKey] + record.ProcessCMDLine = record.Meta[processCMDLineAttrKey] + records = append(records, record) } - - processMemory, ok := row.Data["C"].(float64) - if ok { - record.ProcessMemory = processMemory - } - record.Meta = processAttrs[record.ProcessID] - if processTSInfoMap[record.ProcessID] != nil { - record.ProcessCPUTimeSeries = processTSInfoMap[record.ProcessID].CpuTimeSeries - record.ProcessMemoryTimeSeries = processTSInfoMap[record.ProcessID].MemoryTimeSeries - } - record.ProcessName = record.Meta["process_executable_name"] - record.ProcessCMD = record.Meta["process_command"] - record.ProcessCMDLine = record.Meta["process_command_line"] - records = append(records, record) } - resp.Total = len(records) - - if req.Offset > 0 { - records = records[req.Offset:] - } - if req.Limit > 0 && len(records) > req.Limit { - records = records[:req.Limit] - } + resp.Total = len(allProcessGroups) resp.Records = records - if len(req.GroupBy) > 0 { - groups := []model.ProcessListGroup{} - - groupMap := make(map[string][]model.ProcessListRecord) - for _, record := range records { - groupKey := getGroupKeyForProcesses(record, req.GroupBy) - if _, ok := groupMap[groupKey]; !ok { - groupMap[groupKey] = []model.ProcessListRecord{record} - } else { - groupMap[groupKey] = append(groupMap[groupKey], record) - } - } - - for _, records := range groupMap { - var avgCPU, avgMemory float64 - var validCPU, validMemory int - for _, record := range records { - if !math.IsNaN(record.ProcessCPU) { - avgCPU += record.ProcessCPU - validCPU++ - } - if !math.IsNaN(record.ProcessMemory) { - avgMemory += record.ProcessMemory - validMemory++ - } - } - avgCPU /= float64(validCPU) - avgMemory /= float64(validMemory) - - // take any record and make it as the group meta - firstRecord := records[0] - var groupValues []string - for _, key := range req.GroupBy { - groupValues = append(groupValues, firstRecord.Meta[key.Key]) - } - processNames := []string{} - for _, record := range records { - processNames = append(processNames, record.ProcessName) - } - - groups = append(groups, model.ProcessListGroup{ - GroupValues: groupValues, - GroupCPUAvg: avgCPU, - GroupMemoryAvg: avgMemory, - ProcessNames: processNames, - }) - } - resp.Groups = groups - resp.Type = "grouped_list" - } - return resp, nil } diff --git a/pkg/query-service/app/inframetrics/table_list_query_k8s.go b/pkg/query-service/app/inframetrics/table_list_query_k8s.go deleted file mode 100644 index 68cc9b92d6..0000000000 --- a/pkg/query-service/app/inframetrics/table_list_query_k8s.go +++ /dev/null @@ -1,269 +0,0 @@ -package inframetrics - -import v3 "go.signoz.io/signoz/pkg/query-service/model/v3" - -var K8STableListQuery = v3.QueryRangeParamsV3{ - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{ - { - Key: v3.AttributeKey{ - Key: "state", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeTag, - }, - Operator: v3.FilterOperatorNotEqual, - Value: "idle", - }, - }, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "A", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationRate, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "B": { - QueryName: "B", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "B", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationRate, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "F1": { - QueryName: "F1", - Expression: "A/B", - Legend: "CPU Usage (%)", - }, - "C": { - QueryName: "C", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_memory_usage", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{ - { - Key: v3.AttributeKey{ - Key: "state", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeTag, - }, - Operator: v3.FilterOperatorIn, - Value: []string{"used", "cached"}, - }, - }, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "C", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationAvg, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "D": { - QueryName: "D", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_memory_usage", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "D", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationAvg, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "F2": { - QueryName: "F2", - Expression: "C/D", - Legend: "Memory Usage (%)", - }, - "E": { - QueryName: "E", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{ - { - Key: v3.AttributeKey{ - Key: "state", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeTag, - }, - Operator: v3.FilterOperatorEqual, - Value: "wait", - }, - }, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "E", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationRate, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "F": { - QueryName: "F", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_time", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Cumulative, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "F", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationRate, - SpaceAggregation: v3.SpaceAggregationSum, - Disabled: true, - }, - "F3": { - QueryName: "F3", - Expression: "E/F", - Legend: "CPU Wait Time (%)", - }, - "G": { - QueryName: "G", - DataSource: v3.DataSourceMetrics, - AggregateAttribute: v3.AttributeKey{ - Key: "system_cpu_load_average_15m", - DataType: v3.AttributeKeyDataTypeFloat64, - }, - Temporality: v3.Unspecified, - Filters: &v3.FilterSet{ - Operator: "AND", - Items: []v3.FilterItem{}, - }, - GroupBy: []v3.AttributeKey{ - { - Key: "k8s_node_name", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - { - Key: "os_type", - DataType: v3.AttributeKeyDataTypeString, - Type: v3.AttributeKeyTypeResource, - }, - }, - Expression: "G", - ReduceTo: v3.ReduceToOperatorAvg, - TimeAggregation: v3.TimeAggregationAvg, - SpaceAggregation: v3.SpaceAggregationSum, - Legend: "CPU Load Average (15m)", - }, - }, - PanelType: v3.PanelTypeTable, - QueryType: v3.QueryTypeBuilder, - }, - Version: "v4", - FormatForWeb: true, -} diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index 05855b1ddd..b10cdbac8f 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -85,6 +85,10 @@ func UseMetricsPreAggregation() bool { return GetOrDefaultEnv("USE_METRICS_PRE_AGGREGATION", "true") == "true" } +func EnableHostsInfraMonitoring() bool { + return GetOrDefaultEnv("ENABLE_INFRA_METRICS", "true") == "true" +} + var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false") func IsDurationSortFeatureEnabled() bool { diff --git a/pkg/query-service/model/featureSet.go b/pkg/query-service/model/featureSet.go index de4a4ea879..d6c986619c 100644 --- a/pkg/query-service/model/featureSet.go +++ b/pkg/query-service/model/featureSet.go @@ -23,6 +23,7 @@ const AlertChannelMsTeams = "ALERT_CHANNEL_MSTEAMS" const AlertChannelOpsgenie = "ALERT_CHANNEL_OPSGENIE" const AlertChannelEmail = "ALERT_CHANNEL_EMAIL" const AnomalyDetection = "ANOMALY_DETECTION" +const HostsInfraMonitoring = "HOSTS_INFRA_MONITORING" var BasicPlan = FeatureSet{ Feature{ diff --git a/pkg/query-service/model/infra.go b/pkg/query-service/model/infra.go index 6832113b0c..00cb48ee77 100644 --- a/pkg/query-service/model/infra.go +++ b/pkg/query-service/model/infra.go @@ -22,35 +22,19 @@ type HostListRequest struct { } type HostListRecord struct { - HostName string `json:"hostName"` - Active bool `json:"active"` - OS string `json:"os"` - CPU float64 `json:"cpu"` - CPUTimeSeries *v3.Series `json:"cpuTimeSeries"` - Memory float64 `json:"memory"` - MemoryTimeSeries *v3.Series `json:"memoryTimeSeries"` - Wait float64 `json:"wait"` - WaitTimeSeries *v3.Series `json:"waitTimeSeries"` - Load15 float64 `json:"load15"` - Load15TimeSeries *v3.Series `json:"load15TimeSeries"` - Meta map[string]string `json:"-"` -} - -type HostListGroup struct { - GroupValues []string `json:"groupValues"` - Active int `json:"active"` - Inactive int `json:"inactive"` - GroupCPUAvg float64 `json:"groupCPUAvg"` - GroupMemoryAvg float64 `json:"groupMemoryAvg"` - GroupWaitAvg float64 `json:"groupWaitAvg"` - GroupLoad15Avg float64 `json:"groupLoad15Avg"` - HostNames []string `json:"hostNames"` + HostName string `json:"hostName"` + Active bool `json:"active"` + OS string `json:"os"` + CPU float64 `json:"cpu"` + Memory float64 `json:"memory"` + Wait float64 `json:"wait"` + Load15 float64 `json:"load15"` + Meta map[string]string `json:"meta"` } type HostListResponse struct { - Type string `json:"type"` + Type ResponseType `json:"type"` Records []HostListRecord `json:"records"` - Groups []HostListGroup `json:"groups"` Total int `json:"total"` } @@ -65,29 +49,19 @@ type ProcessListRequest struct { } type ProcessListResponse struct { - Type string `json:"type"` + Type ResponseType `json:"type"` Records []ProcessListRecord `json:"records"` - Groups []ProcessListGroup `json:"groups"` Total int `json:"total"` } type ProcessListRecord struct { - ProcessName string `json:"processName"` - ProcessID string `json:"processID"` - ProcessCMD string `json:"processCMD"` - ProcessCMDLine string `json:"processCMDLine"` - ProcessCPU float64 `json:"processCPU"` - ProcessCPUTimeSeries *v3.Series `json:"processCPUTimeSeries"` - ProcessMemory float64 `json:"processMemory"` - ProcessMemoryTimeSeries *v3.Series `json:"processMemoryTimeSeries"` - Meta map[string]string `json:"-"` -} - -type ProcessListGroup struct { - GroupValues []string `json:"groupValues"` - GroupCPUAvg float64 `json:"groupCPUAvg"` - GroupMemoryAvg float64 `json:"groupMemoryAvg"` - ProcessNames []string `json:"processNames"` + ProcessName string `json:"processName"` + ProcessID string `json:"processID"` + ProcessCMD string `json:"processCMD"` + ProcessCMDLine string `json:"processCMDLine"` + ProcessCPU float64 `json:"processCPU"` + ProcessMemory float64 `json:"processMemory"` + Meta map[string]string `json:"meta"` } type PodListRequest struct { From 5005923ef47a7257d14d8c513cde9c6f6399398b Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 4 Nov 2024 15:19:05 +0530 Subject: [PATCH 33/52] fix: re add threshold for promql alerts (#6355) --- .../container/FormAlertRules/RuleOptions.tsx | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/frontend/src/container/FormAlertRules/RuleOptions.tsx b/frontend/src/container/FormAlertRules/RuleOptions.tsx index 969e34b958..e9aa8f860f 100644 --- a/frontend/src/container/FormAlertRules/RuleOptions.tsx +++ b/frontend/src/container/FormAlertRules/RuleOptions.tsx @@ -386,32 +386,31 @@ function RuleOptions({ renderThresholdRuleOpts()} - {queryCategory !== EQueryType.PROM && - ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && ( - - - e.currentTarget.blur()} - /> - + {ruleType !== AlertDetectionTypes.ANOMALY_DETECTION_ALERT && ( + + + e.currentTarget.blur()} + /> + - - + + + )} From 9d90b8d19c0cffec41680709a1ad06b016cc9b53 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 4 Nov 2024 23:58:38 +0530 Subject: [PATCH 34/52] chore: github wf update pr labels and block pr until related docs are shipped for the feature (#6333) --- .github/workflows/docs.yml | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..de2de959da --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,83 @@ +name: "Update PR labels and Block PR until related docs are shipped for the feature" + +on: + pull_request: + branches: + - develop + types: [opened, edited, labeled, unlabeled] + +permissions: + pull-requests: write + contents: read + +jobs: + docs_label_check: + runs-on: ubuntu-latest + steps: + - name: Check PR Title and Manage Labels + uses: actions/github-script@v6 + with: + script: | + const prTitle = context.payload.pull_request.title; + const prNumber = context.payload.pull_request.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + // Fetch the current PR details to get labels + const pr = await github.rest.pulls.get({ + owner, + repo, + pull_number: prNumber + }); + + const labels = pr.data.labels.map(label => label.name); + + if (prTitle.startsWith('feat:')) { + const hasDocsRequired = labels.includes('docs required'); + const hasDocsShipped = labels.includes('docs shipped'); + const hasDocsNotRequired = labels.includes('docs not required'); + + // If "docs not required" is present, skip the checks + if (hasDocsNotRequired && !hasDocsRequired) { + console.log("Skipping checks due to 'docs not required' label."); + return; // Exit the script early + } + + // If "docs shipped" is present, remove "docs required" if it exists + if (hasDocsShipped && hasDocsRequired) { + await github.rest.issues.removeLabel({ + owner, + repo, + issue_number: prNumber, + name: 'docs required' + }); + console.log("Removed 'docs required' label."); + } + + // Add "docs required" label if neither "docs shipped" nor "docs required" are present + if (!hasDocsRequired && !hasDocsShipped) { + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: prNumber, + labels: ['docs required'] + }); + console.log("Added 'docs required' label."); + } + } + + // Fetch the updated labels after any changes + const updatedPr = await github.rest.pulls.get({ + owner, + repo, + pull_number: prNumber + }); + + const updatedLabels = updatedPr.data.labels.map(label => label.name); + const updatedHasDocsRequired = updatedLabels.includes('docs required'); + const updatedHasDocsShipped = updatedLabels.includes('docs shipped'); + + // Block PR if "docs required" is still present and "docs shipped" is missing + if (updatedHasDocsRequired && !updatedHasDocsShipped) { + core.setFailed("This PR requires documentation. Please remove the 'docs required' label and add the 'docs shipped' label to proceed."); + } From 12377be809e062ba70d2317371d266353b1c3476 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:26:41 +0530 Subject: [PATCH 35/52] feat: added generic UI for scenario 1,3,4 (#6287) * feat: added generic table component for scenario 1,3,4 * feat: added generic logic for mq detail tables and consumed for sc-1,2 * feat: added overview and details table for scenario-3 * feat: added table row clicks func * feat: resolved comments --- frontend/src/constants/query.ts | 1 + .../MQDetailPage/MQDetailPage.tsx | 50 ++-- .../MQDetails/MQDetails.style.scss | 39 +++ .../MessagingQueues/MQDetails/MQDetails.tsx | 228 +++++++++++++++--- .../MQDetails/MQTables/MQTables.styles.scss | 21 +- .../MQDetails/MQTables/MQTables.tsx | 165 ++++++++----- .../MQTables/getConsumerLagDetails.ts | 9 +- .../MQTables/getPartitionLatencyDetails.ts | 39 +++ .../MQTables/getPartitionLatencyOverview.ts | 34 +++ .../MQTables/getTopicThroughputDetails.ts | 33 +++ .../MQTables/getTopicThroughputOverview.ts | 37 +++ .../MQDetails/MessagingQueueOverview.tsx | 102 ++++++++ .../MessagingQueues.styles.scss | 2 + .../MessagingQueues/MessagingQueuesUtils.ts | 133 +++++++++- 14 files changed, 770 insertions(+), 123 deletions(-) create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyDetails.ts create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyOverview.ts create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputDetails.ts create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputOverview.ts create mode 100644 frontend/src/pages/MessagingQueues/MQDetails/MessagingQueueOverview.tsx diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index 2214e9487c..a6e9ee41c4 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -37,4 +37,5 @@ export enum QueryParams { partition = 'partition', selectedTimelineQuery = 'selectedTimelineQuery', ruleType = 'ruleType', + configDetail = 'configDetail', } diff --git a/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx index 8fa697f6af..931502b8e1 100644 --- a/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetailPage/MQDetailPage.tsx @@ -5,17 +5,33 @@ import logEvent from 'api/common/logEvent'; import ROUTES from 'constants/routes'; import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2'; import { ListMinus } from 'lucide-react'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { MessagingQueuesViewType } from '../MessagingQueuesUtils'; +import { + MessagingQueuesViewType, + MessagingQueuesViewTypeOptions, + ProducerLatencyOptions, +} from '../MessagingQueuesUtils'; import { SelectLabelWithComingSoon } from '../MQCommon/MQCommon'; +import MessagingQueueOverview from '../MQDetails/MessagingQueueOverview'; import MessagingQueuesDetails from '../MQDetails/MQDetails'; import MessagingQueuesConfigOptions from '../MQGraph/MQConfigOptions'; import MessagingQueuesGraph from '../MQGraph/MQGraph'; function MQDetailPage(): JSX.Element { const history = useHistory(); + const [ + selectedView, + setSelectedView, + ] = useState( + MessagingQueuesViewType.consumerLag.value, + ); + + const [ + producerLatencyOption, + setproducerLatencyOption, + ] = useState(ProducerLatencyOptions.Producers); useEffect(() => { logEvent('Messaging Queues: Detail page visited', {}); @@ -39,28 +55,19 @@ function MQDetailPage(): JSX.Element { className="messaging-queue-options" defaultValue={MessagingQueuesViewType.consumerLag.value} popupClassName="messaging-queue-options-popup" + onChange={(value): void => setSelectedView(value)} options={[ { label: MessagingQueuesViewType.consumerLag.label, value: MessagingQueuesViewType.consumerLag.value, }, { - label: ( - - ), + label: MessagingQueuesViewType.partitionLatency.label, value: MessagingQueuesViewType.partitionLatency.value, - disabled: true, }, { - label: ( - - ), + label: MessagingQueuesViewType.producerLatency.label, value: MessagingQueuesViewType.producerLatency.value, - disabled: true, }, { label: ( @@ -78,10 +85,21 @@ function MQDetailPage(): JSX.Element {
- + {selectedView === MessagingQueuesViewType.consumerLag.value ? ( + + ) : ( + + )}
- +
); diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss index 68014823da..c4995a1812 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss @@ -4,3 +4,42 @@ flex-direction: column; gap: 24px; } + +.mq-overview-container { + display: flex; + padding: 24px; + flex-direction: column; + align-items: start; + gap: 16px; + + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + background: var(--bg-ink-500); + + .mq-overview-title { + color: var(--bg-vanilla-200); + + font-family: Inter; + font-size: 18px; + font-style: normal; + font-weight: 500; + line-height: 28px; + } + + .mq-details-options { + letter-spacing: -0.06px; + cursor: pointer; + + .ant-radio-button-wrapper { + border-color: var(--bg-slate-400); + color: var(--bg-vanilla-400); + } + .ant-radio-button-wrapper-checked { + background: var(--bg-slate-400); + color: var(--bg-vanilla-100); + } + .ant-radio-button-wrapper::before { + width: 0px; + } + } +} diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx index df1f643daa..6ec3d45f28 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx @@ -1,65 +1,227 @@ import './MQDetails.style.scss'; import { Radio } from 'antd'; -import { Dispatch, SetStateAction, useState } from 'react'; +import { QueryParams } from 'constants/query'; +import useUrlQuery from 'hooks/useUrlQuery'; +import { isEmpty } from 'lodash-es'; +import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; import { ConsumerLagDetailTitle, - ConsumerLagDetailType, + getMetaDataAndAPIPerView, + MessagingQueueServiceDetailType, + MessagingQueuesViewType, + MessagingQueuesViewTypeOptions, + ProducerLatencyOptions, + SelectedTimelineQuery, } from '../MessagingQueuesUtils'; import { ComingSoon } from '../MQCommon/MQCommon'; import MessagingQueuesTable from './MQTables/MQTables'; +const MQServiceDetailTypePerView = ( + producerLatencyOption: ProducerLatencyOptions, +): Record => ({ + [MessagingQueuesViewType.consumerLag.value]: [ + MessagingQueueServiceDetailType.ConsumerDetails, + MessagingQueueServiceDetailType.ProducerDetails, + MessagingQueueServiceDetailType.NetworkLatency, + MessagingQueueServiceDetailType.PartitionHostMetrics, + ], + [MessagingQueuesViewType.partitionLatency.value]: [ + MessagingQueueServiceDetailType.ConsumerDetails, + MessagingQueueServiceDetailType.ProducerDetails, + ], + [MessagingQueuesViewType.producerLatency.value]: [ + producerLatencyOption === ProducerLatencyOptions.Consumers + ? MessagingQueueServiceDetailType.ConsumerDetails + : MessagingQueueServiceDetailType.ProducerDetails, + ], +}); + +interface MessagingQueuesOptionsProps { + currentTab: MessagingQueueServiceDetailType; + setCurrentTab: Dispatch>; + selectedView: MessagingQueuesViewTypeOptions; + producerLatencyOption: ProducerLatencyOptions; +} + function MessagingQueuesOptions({ currentTab, setCurrentTab, -}: { - currentTab: ConsumerLagDetailType; - setCurrentTab: Dispatch>; -}): JSX.Element { - const [option, setOption] = useState(currentTab); + selectedView, + producerLatencyOption, +}: MessagingQueuesOptionsProps): JSX.Element { + const handleChange = (value: MessagingQueueServiceDetailType): void => { + setCurrentTab(value); + }; + + const renderRadioButtons = (): JSX.Element[] => { + const detailTypes = + MQServiceDetailTypePerView(producerLatencyOption)[selectedView] || []; + return detailTypes.map((detailType) => ( + + {ConsumerLagDetailTitle[detailType]} + {detailType === MessagingQueueServiceDetailType.PartitionHostMetrics && ( + + )} + + )); + }; return ( { - setOption(value.target.value); - setCurrentTab(value.target.value); - }} - value={option} + onChange={(e): void => handleChange(e.target.value)} + value={currentTab} className="mq-details-options" > - - {ConsumerLagDetailTitle[ConsumerLagDetailType.ConsumerDetails]} - - - {ConsumerLagDetailTitle[ConsumerLagDetailType.ProducerDetails]} - - - {ConsumerLagDetailTitle[ConsumerLagDetailType.NetworkLatency]} - - - {ConsumerLagDetailTitle[ConsumerLagDetailType.PartitionHostMetrics]} - - + {renderRadioButtons()} ); } -function MessagingQueuesDetails(): JSX.Element { - const [currentTab, setCurrentTab] = useState( - ConsumerLagDetailType.ConsumerDetails, +const checkValidityOfDetailConfigs = ( + selectedTimelineQuery: SelectedTimelineQuery, + selectedView: MessagingQueuesViewTypeOptions, + currentTab: MessagingQueueServiceDetailType, + configDetails?: { + [key: string]: string; + }, + // eslint-disable-next-line sonarjs/cognitive-complexity +): boolean => { + if (selectedView === MessagingQueuesViewType.consumerLag.value) { + return !( + isEmpty(selectedTimelineQuery) || + (!selectedTimelineQuery?.group && + !selectedTimelineQuery?.topic && + !selectedTimelineQuery?.partition) + ); + } + + if (selectedView === MessagingQueuesViewType.partitionLatency.value) { + if (isEmpty(configDetails)) { + return false; + } + + if (currentTab === MessagingQueueServiceDetailType.ConsumerDetails) { + return Boolean(configDetails?.topic && configDetails?.partition); + } + return Boolean( + configDetails?.group && configDetails?.topic && configDetails?.partition, + ); + } + + if (selectedView === MessagingQueuesViewType.producerLatency.value) { + if (isEmpty(configDetails)) { + return false; + } + + if (currentTab === MessagingQueueServiceDetailType.ProducerDetails) { + return Boolean( + configDetails?.topic && + configDetails?.partition && + configDetails?.service_name, + ); + } + return Boolean(configDetails?.topic && configDetails?.service_name); + } + + return false; +}; + +function MessagingQueuesDetails({ + selectedView, + producerLatencyOption, +}: { + selectedView: MessagingQueuesViewTypeOptions; + producerLatencyOption: ProducerLatencyOptions; +}): JSX.Element { + const [currentTab, setCurrentTab] = useState( + MessagingQueueServiceDetailType.ConsumerDetails, ); + + useEffect(() => { + if ( + producerLatencyOption && + selectedView === MessagingQueuesViewType.producerLatency.value + ) { + setCurrentTab( + producerLatencyOption === ProducerLatencyOptions.Consumers + ? MessagingQueueServiceDetailType.ConsumerDetails + : MessagingQueueServiceDetailType.ProducerDetails, + ); + } + }, [selectedView, producerLatencyOption]); + + const urlQuery = useUrlQuery(); + const timelineQuery = decodeURIComponent( + urlQuery.get(QueryParams.selectedTimelineQuery) || '', + ); + + const timelineQueryData: SelectedTimelineQuery = useMemo( + () => (timelineQuery ? JSON.parse(timelineQuery) : {}), + [timelineQuery], + ); + + const configDetails = decodeURIComponent( + urlQuery.get(QueryParams.configDetail) || '', + ); + + const configDetailQueryData: { + [key: string]: string; + } = useMemo(() => (configDetails ? JSON.parse(configDetails) : {}), [ + configDetails, + ]); + + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const serviceConfigDetails = useMemo( + () => + getMetaDataAndAPIPerView({ + detailType: currentTab, + minTime, + maxTime, + selectedTimelineQuery: timelineQueryData, + configDetails: configDetailQueryData, + }), + [configDetailQueryData, currentTab, maxTime, minTime, timelineQueryData], + ); + return (
+ -
); } diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.styles.scss b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.styles.scss index e02e19e890..ad665d61f5 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.styles.scss +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.styles.scss @@ -1,4 +1,7 @@ .mq-tables-container { + width: 100%; + height: 100%; + .mq-table-title { display: flex; align-items: center; @@ -31,9 +34,6 @@ .ant-table-tbody { .ant-table-cell { max-width: 250px; - - background-color: var(--bg-ink-400); - border-bottom: none; } } @@ -63,6 +63,21 @@ } } +.mq-table { + &.mq-overview-row-clickable { + .ant-table-row { + background-color: var(--bg-ink-400); + + &:hover { + cursor: pointer; + background-color: var(--bg-slate-400) !important; + color: var(--bg-vanilla-400); + transition: background-color 0.3s ease, color 0.3s ease; + } + } + } +} + .lightMode { .mq-tables-container { .mq-table-title { diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx index 9555f7f228..52c01fce45 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx @@ -1,9 +1,10 @@ +/* eslint-disable react/require-default-props */ import './MQTables.styles.scss'; import { Skeleton, Table, Typography } from 'antd'; -import logEvent from 'api/common/logEvent'; import axios from 'axios'; import { isNumber } from 'chart.js/helpers'; +import cx from 'classnames'; import { ColumnTypeRender } from 'components/Logs/TableView/types'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import { QueryParams } from 'constants/query'; @@ -13,18 +14,21 @@ import useUrlQuery from 'hooks/useUrlQuery'; import { isEmpty } from 'lodash-es'; import { ConsumerLagDetailTitle, - ConsumerLagDetailType, convertToTitleCase, + MessagingQueueServiceDetailType, + MessagingQueuesViewType, + MessagingQueuesViewTypeOptions, RowData, SelectedTimelineQuery, + setConfigDetail, } from 'pages/MessagingQueues/MessagingQueuesUtils'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { useMutation } from 'react-query'; -import { useHistory } from 'react-router-dom'; +import { useHistory, useLocation } from 'react-router-dom'; +import { ErrorResponse, SuccessResponse } from 'types/api'; import { - ConsumerLagPayload, - getConsumerLagDetails, + MessagingQueueServicePayload, MessagingQueuesPayloadProps, } from './getConsumerLagDetails'; @@ -33,7 +37,6 @@ export function getColumns( data: MessagingQueuesPayloadProps['payload'], history: History, ): RowData[] { - console.log(data); if (data?.result?.length === 0) { return []; } @@ -105,10 +108,25 @@ const showPaginationItem = (total: number, range: number[]): JSX.Element => ( ); +// eslint-disable-next-line sonarjs/cognitive-complexity function MessagingQueuesTable({ currentTab, + selectedView, + tableApiPayload, + tableApi, + validConfigPresent = false, + type = 'Detail', }: { - currentTab: ConsumerLagDetailType; + currentTab?: MessagingQueueServiceDetailType; + selectedView: MessagingQueuesViewTypeOptions; + tableApiPayload?: MessagingQueueServicePayload; + tableApi: ( + props: MessagingQueueServicePayload, + ) => Promise< + SuccessResponse | ErrorResponse + >; + validConfigPresent?: boolean; + type?: 'Detail' | 'Overview'; }): JSX.Element { const [columns, setColumns] = useState([]); const [tableData, setTableData] = useState([]); @@ -118,11 +136,22 @@ function MessagingQueuesTable({ const timelineQuery = decodeURIComponent( urlQuery.get(QueryParams.selectedTimelineQuery) || '', ); + const timelineQueryData: SelectedTimelineQuery = useMemo( () => (timelineQuery ? JSON.parse(timelineQuery) : {}), [timelineQuery], ); + const configDetails = decodeURIComponent( + urlQuery.get(QueryParams.configDetail) || '', + ); + + const configDetailQueryData: { + [key: string]: string; + } = useMemo(() => (configDetails ? JSON.parse(configDetails) : {}), [ + configDetails, + ]); + const paginationConfig = useMemo( () => tableData?.length > 20 && { @@ -134,90 +163,104 @@ function MessagingQueuesTable({ [tableData], ); - const props: ConsumerLagPayload = useMemo( - () => ({ - start: (timelineQueryData?.start || 0) * 1e9, - end: (timelineQueryData?.end || 0) * 1e9, - variables: { - partition: timelineQueryData?.partition, - topic: timelineQueryData?.topic, - consumer_group: timelineQueryData?.group, - }, - detailType: currentTab, - }), - [currentTab, timelineQueryData], - ); - const handleConsumerDetailsOnError = (error: Error): void => { notifications.error({ message: axios.isAxiosError(error) ? error?.message : SOMETHING_WENT_WRONG, }); }; - const { mutate: getConsumerDetails, isLoading } = useMutation( - getConsumerLagDetails, - { - onSuccess: (data) => { - if (data.payload) { - setColumns(getColumns(data?.payload, history)); - setTableData(getTableData(data?.payload)); - } - }, - onError: handleConsumerDetailsOnError, + const { mutate: getViewDetails, isLoading } = useMutation(tableApi, { + onSuccess: (data) => { + if (data.payload) { + setColumns(getColumns(data?.payload, history)); + setTableData(getTableData(data?.payload)); + } }, + onError: handleConsumerDetailsOnError, + }); + + useEffect( + () => { + if (validConfigPresent && tableApiPayload) { + getViewDetails(tableApiPayload); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [currentTab, selectedView, tableApiPayload], ); - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => getConsumerDetails(props), [currentTab, props]); + const [selectedRowKey, setSelectedRowKey] = useState(); + const [, setSelectedRows] = useState(); + const location = useLocation(); - const isLogEventCalled = useRef(false); + const onRowClick = (record: { [key: string]: string }): void => { + const selectedKey = record.key; - const isEmptyDetails = (timelineQueryData: SelectedTimelineQuery): boolean => { - const isEmptyDetail = - isEmpty(timelineQueryData) || - (!timelineQueryData?.group && - !timelineQueryData?.topic && - !timelineQueryData?.partition); + if (`${selectedKey}_${selectedView}` === selectedRowKey) { + setSelectedRowKey(undefined); + setSelectedRows({}); + setConfigDetail(urlQuery, location, history, {}); + } else { + setSelectedRowKey(`${selectedKey}_${selectedView}`); + setSelectedRows(record); - if (!isEmptyDetail && !isLogEventCalled.current) { - logEvent('Messaging Queues: More details viewed', { - 'tab-option': ConsumerLagDetailTitle[currentTab], - variables: { - group: timelineQueryData?.group, - topic: timelineQueryData?.topic, - partition: timelineQueryData?.partition, - }, - }); - isLogEventCalled.current = true; + if (!isEmpty(record)) { + setConfigDetail(urlQuery, location, history, record); + } } - return isEmptyDetail; }; + const subtitle = + selectedView === MessagingQueuesViewType.consumerLag.value + ? `${timelineQueryData?.group || ''} ${timelineQueryData?.topic || ''} ${ + timelineQueryData?.partition || '' + }` + : `${configDetailQueryData?.service_name || ''} ${ + configDetailQueryData?.topic || '' + } ${configDetailQueryData?.partition || ''}`; + return (
- {isEmptyDetails(timelineQueryData) ? ( + {!validConfigPresent ? (
- Click on a co-ordinate above to see the details + {selectedView === MessagingQueuesViewType.consumerLag.value + ? 'Click on a co-ordinate above to see the details' + : 'Click on a row above to see the details'}
) : ( <> -
- {ConsumerLagDetailTitle[currentTab]} -
{`${timelineQueryData?.group || ''} ${ - timelineQueryData?.topic || '' - } ${timelineQueryData?.partition || ''}`}
-
+ {currentTab && ( +
+ {ConsumerLagDetailTitle[currentTab]} +
{subtitle}
+
+ )} + type !== 'Detail' + ? { + onClick: (): void => onRowClick(record), + } + : {} + } + rowClassName={(record): any => + `${record.key}_${selectedView}` === selectedRowKey + ? 'ant-table-row-selected' + : '' + } /> )} diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails.ts b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails.ts index fc6a273e5d..ad8a290dd7 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails.ts +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getConsumerLagDetails.ts @@ -2,18 +2,19 @@ import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { SOMETHING_WENT_WRONG } from 'constants/api'; -import { ConsumerLagDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils'; +import { MessagingQueueServiceDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { ErrorResponse, SuccessResponse } from 'types/api'; -export interface ConsumerLagPayload { +export interface MessagingQueueServicePayload { start?: number | string; end?: number | string; variables: { partition?: string; topic?: string; consumer_group?: string; + service_name?: string; }; - detailType: ConsumerLagDetailType; + detailType?: MessagingQueueServiceDetailType | 'producer' | 'consumer'; } export interface MessagingQueuesPayloadProps { @@ -36,7 +37,7 @@ export interface MessagingQueuesPayloadProps { } export const getConsumerLagDetails = async ( - props: ConsumerLagPayload, + props: MessagingQueueServicePayload, ): Promise< SuccessResponse | ErrorResponse > => { diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyDetails.ts b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyDetails.ts new file mode 100644 index 0000000000..8c0b26f594 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyDetails.ts @@ -0,0 +1,39 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { MessagingQueueServiceDetailType } from 'pages/MessagingQueues/MessagingQueuesUtils'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +import { + MessagingQueueServicePayload, + MessagingQueuesPayloadProps, +} from './getConsumerLagDetails'; + +export const getPartitionLatencyDetails = async ( + props: MessagingQueueServicePayload, +): Promise< + SuccessResponse | ErrorResponse +> => { + const { detailType, ...rest } = props; + let endpoint = ''; + if (detailType === MessagingQueueServiceDetailType.ConsumerDetails) { + endpoint = `/messaging-queues/kafka/partition-latency/consumer`; + } else { + endpoint = `/messaging-queues/kafka/consumer-lag/producer-details`; + } + try { + const response = await axios.post(endpoint, { + ...rest, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG); + } +}; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyOverview.ts b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyOverview.ts new file mode 100644 index 0000000000..49c8eed757 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getPartitionLatencyOverview.ts @@ -0,0 +1,34 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +import { + MessagingQueueServicePayload, + MessagingQueuesPayloadProps, +} from './getConsumerLagDetails'; + +export const getPartitionLatencyOverview = async ( + props: Omit, +): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.post( + `/messaging-queues/kafka/partition-latency/overview`, + { + ...props, + }, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG); + } +}; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputDetails.ts b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputDetails.ts new file mode 100644 index 0000000000..3a995ef590 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputDetails.ts @@ -0,0 +1,33 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +import { + MessagingQueueServicePayload, + MessagingQueuesPayloadProps, +} from './getConsumerLagDetails'; + +export const getTopicThroughputDetails = async ( + props: MessagingQueueServicePayload, +): Promise< + SuccessResponse | ErrorResponse +> => { + const { detailType, ...rest } = props; + const endpoint = `/messaging-queues/kafka/topic-throughput/${detailType}`; + try { + const response = await axios.post(endpoint, { + ...rest, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG); + } +}; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputOverview.ts b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputOverview.ts new file mode 100644 index 0000000000..7ed896d8ca --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/getTopicThroughputOverview.ts @@ -0,0 +1,37 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +import { + MessagingQueueServicePayload, + MessagingQueuesPayloadProps, +} from './getConsumerLagDetails'; + +export const getTopicThroughputOverview = async ( + props: Omit, +): Promise< + SuccessResponse | ErrorResponse +> => { + const { detailType, start, end } = props; + console.log(detailType); + try { + const response = await axios.post( + `messaging-queues/kafka/topic-throughput/${detailType}`, + { + start, + end, + }, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG); + } +}; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MessagingQueueOverview.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MessagingQueueOverview.tsx new file mode 100644 index 0000000000..104b7ec237 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/MessagingQueueOverview.tsx @@ -0,0 +1,102 @@ +import './MQDetails.style.scss'; + +import { Radio } from 'antd'; +import { Dispatch, SetStateAction } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +import { + MessagingQueuesViewType, + MessagingQueuesViewTypeOptions, + ProducerLatencyOptions, +} from '../MessagingQueuesUtils'; +import { MessagingQueueServicePayload } from './MQTables/getConsumerLagDetails'; +import { getPartitionLatencyOverview } from './MQTables/getPartitionLatencyOverview'; +import { getTopicThroughputOverview } from './MQTables/getTopicThroughputOverview'; +import MessagingQueuesTable from './MQTables/MQTables'; + +type SelectedViewType = keyof typeof MessagingQueuesViewType; + +function PartitionLatencyTabs({ + option, + setOption, +}: { + option: ProducerLatencyOptions; + setOption: Dispatch>; +}): JSX.Element { + return ( + setOption(e.target.value)} + value={option} + className="mq-details-options" + > + + {ProducerLatencyOptions.Producers} + + + {ProducerLatencyOptions.Consumers} + + + ); +} + +const getTableApi = (selectedView: MessagingQueuesViewTypeOptions): any => { + if (selectedView === MessagingQueuesViewType.producerLatency.value) { + return getTopicThroughputOverview; + } + return getPartitionLatencyOverview; +}; + +function MessagingQueueOverview({ + selectedView, + option, + setOption, +}: { + selectedView: MessagingQueuesViewTypeOptions; + option: ProducerLatencyOptions; + setOption: Dispatch>; +}): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const tableApiPayload: MessagingQueueServicePayload = { + variables: {}, + start: minTime, + end: maxTime, + detailType: + // eslint-disable-next-line no-nested-ternary + selectedView === MessagingQueuesViewType.producerLatency.value + ? option === ProducerLatencyOptions.Producers + ? 'producer' + : 'consumer' + : undefined, + }; + + return ( +
+ {selectedView === MessagingQueuesViewType.producerLatency.value ? ( + + ) : ( +
+ {MessagingQueuesViewType[selectedView as SelectedViewType].label} +
+ )} + +
+ ); +} +export default MessagingQueueOverview; diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss index be2a27b50b..d4a1d1165c 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss @@ -106,6 +106,8 @@ .mq-details-options { letter-spacing: -0.06px; + cursor: pointer; + .ant-radio-button-wrapper { border-color: var(--bg-slate-400); color: var(--bg-vanilla-400); diff --git a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts index d215b1025e..062e4317c2 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts +++ b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts @@ -3,12 +3,21 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types'; import { History, Location } from 'history'; import { isEmpty } from 'lodash-es'; +import { ErrorResponse, SuccessResponse } from 'types/api'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; import { EQueryType } from 'types/common/dashboard'; import { DataSource } from 'types/common/queryBuilder'; import { v4 as uuid } from 'uuid'; +import { + getConsumerLagDetails, + MessagingQueueServicePayload, + MessagingQueuesPayloadProps, +} from './MQDetails/MQTables/getConsumerLagDetails'; +import { getPartitionLatencyDetails } from './MQDetails/MQTables/getPartitionLatencyDetails'; +import { getTopicThroughputDetails } from './MQDetails/MQTables/getTopicThroughputDetails'; + export const KAFKA_SETUP_DOC_LINK = 'https://signoz.io/docs/messaging-queues/kafka?utm_source=product&utm_medium=kafka-get-started'; @@ -24,14 +33,17 @@ export type RowData = { [key: string]: string | number; }; -export enum ConsumerLagDetailType { +export enum MessagingQueueServiceDetailType { ConsumerDetails = 'consumer-details', ProducerDetails = 'producer-details', NetworkLatency = 'network-latency', PartitionHostMetrics = 'partition-host-metric', } -export const ConsumerLagDetailTitle: Record = { +export const ConsumerLagDetailTitle: Record< + MessagingQueueServiceDetailType, + string +> = { 'consumer-details': 'Consumer Groups Details', 'producer-details': 'Producer Details', 'network-latency': 'Network Latency', @@ -205,21 +217,130 @@ export function setSelectedTimelineQuery( history.replace(generatedUrl); } +export enum MessagingQueuesViewTypeOptions { + ConsumerLag = 'consumerLag', + PartitionLatency = 'partitionLatency', + ProducerLatency = 'producerLatency', + ConsumerLatency = 'consumerLatency', +} + export const MessagingQueuesViewType = { consumerLag: { label: 'Consumer Lag view', - value: 'consumerLag', + value: MessagingQueuesViewTypeOptions.ConsumerLag, }, partitionLatency: { label: 'Partition Latency view', - value: 'partitionLatency', + value: MessagingQueuesViewTypeOptions.PartitionLatency, }, producerLatency: { label: 'Producer Latency view', - value: 'producerLatency', + value: MessagingQueuesViewTypeOptions.ProducerLatency, }, consumerLatency: { label: 'Consumer latency view', - value: 'consumerLatency', + value: MessagingQueuesViewTypeOptions.ConsumerLatency, }, }; + +export function setConfigDetail( + urlQuery: URLSearchParams, + location: Location, + history: History, + paramsToSet?: { + [key: string]: string; + }, +): void { + // remove "key" and its value from the paramsToSet object + const { key, ...restParamsToSet } = paramsToSet || {}; + + if (!isEmpty(restParamsToSet)) { + const configDetail = { + ...restParamsToSet, + }; + urlQuery.set( + QueryParams.configDetail, + encodeURIComponent(JSON.stringify(configDetail)), + ); + } else { + urlQuery.delete(QueryParams.configDetail); + } + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.replace(generatedUrl); +} + +export enum ProducerLatencyOptions { + Producers = 'Producers', + Consumers = 'Consumers', +} + +interface MetaDataAndAPI { + tableApiPayload: MessagingQueueServicePayload; + tableApi: ( + props: MessagingQueueServicePayload, + ) => Promise< + SuccessResponse | ErrorResponse + >; +} +interface MetaDataAndAPIPerView { + detailType: MessagingQueueServiceDetailType; + selectedTimelineQuery: SelectedTimelineQuery; + configDetails?: { + [key: string]: string; + }; + minTime: number; + maxTime: number; +} + +export const getMetaDataAndAPIPerView = ( + metaDataProps: MetaDataAndAPIPerView, +): Record => { + const { + detailType, + minTime, + maxTime, + selectedTimelineQuery, + configDetails, + } = metaDataProps; + return { + [MessagingQueuesViewType.consumerLag.value]: { + tableApiPayload: { + start: (selectedTimelineQuery?.start || 0) * 1e9, + end: (selectedTimelineQuery?.end || 0) * 1e9, + variables: { + partition: selectedTimelineQuery?.partition, + topic: selectedTimelineQuery?.topic, + consumer_group: selectedTimelineQuery?.group, + }, + detailType, + }, + tableApi: getConsumerLagDetails, + }, + [MessagingQueuesViewType.partitionLatency.value]: { + tableApiPayload: { + start: minTime, + end: maxTime, + variables: { + partition: configDetails?.partition, + topic: configDetails?.topic, + consumer_group: configDetails?.group, + }, + detailType, + }, + tableApi: getPartitionLatencyDetails, + }, + [MessagingQueuesViewType.producerLatency.value]: { + tableApiPayload: { + start: minTime, + end: maxTime, + variables: { + partition: configDetails?.partition, + topic: configDetails?.topic, + service_name: configDetails?.service_name, + }, + detailType, + }, + tableApi: getTopicThroughputDetails, + }, + }; +}; From 975307a8b8f9d8d60265c51de8564ceca200a994 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:40:23 +0530 Subject: [PATCH 36/52] feat: added onboarding setup for Producer for Messaging queues (#6236) * fix: added onboarding setup for producer/consumer for Messaging queues * fix: polled onboarding status api * feat: added onboarding status api with useQueury functions and updated endpoints * feat: added onboarding status api util for attribute data * feat: refactoring and url query changes * feat: changed start and end time to nanosecond for api payload * feat: added comment description --- .../onboarding/getOnboardingStatus.ts | 37 ++++++ frontend/src/constants/query.ts | 2 + ...Boot-kubernetes-runApplication-producer.md | 29 ++++ .../ConnectionStatus/ConnectionStatus.tsx | 125 ++++++++++++++---- .../Steps/DataSource/DataSource.styles.scss | 9 +- .../Steps/DataSource/DataSource.tsx | 19 ++- .../Steps/MarkdownStep/MarkdownStep.tsx | 14 ++ .../constants/apmDocFilePaths.ts | 2 + .../utils/dataSourceUtils.ts | 2 + .../ onboarding/useOnboardingStatus.tsx | 22 +++ .../pages/MessagingQueues/MessagingQueues.tsx | 7 +- .../MessagingQueues/MessagingQueuesUtils.ts | 37 ++++++ 12 files changed, 272 insertions(+), 33 deletions(-) create mode 100644 frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts create mode 100644 frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md create mode 100644 frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx diff --git a/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts b/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts new file mode 100644 index 0000000000..b5da83aa13 --- /dev/null +++ b/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts @@ -0,0 +1,37 @@ +import { ApiBaseInstance } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +export interface OnboardingStatusResponse { + status: string; + data: { + attribute?: string; + error_message?: string; + status?: string; + }[]; +} + +const getOnboardingStatus = async (props: { + start: number; + end: number; +}): Promise | ErrorResponse> => { + try { + const response = await ApiBaseInstance.post( + '/messaging-queues/kafka/onboarding/consumers', + props, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler((error as AxiosError) || SOMETHING_WENT_WRONG); + } +}; + +export default getOnboardingStatus; diff --git a/frontend/src/constants/query.ts b/frontend/src/constants/query.ts index a6e9ee41c4..7d3cb2603d 100644 --- a/frontend/src/constants/query.ts +++ b/frontend/src/constants/query.ts @@ -38,4 +38,6 @@ export enum QueryParams { selectedTimelineQuery = 'selectedTimelineQuery', ruleType = 'ruleType', configDetail = 'configDetail', + getStartedSource = 'getStartedSource', + getStartedSourceService = 'getStartedSourceService', } diff --git a/frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md b/frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md new file mode 100644 index 0000000000..2c463dbf07 --- /dev/null +++ b/frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md @@ -0,0 +1,29 @@ +  + +Once you are done intrumenting your Java application, you can run it using the below commands + +**Note:** +- Ensure you have Java and Maven installed. Compile your Java producer applications: Ensure your producer and consumer apps are compiled and ready to run. + +**Run Producer App with Java Agent:** + +```bash +java -javaagent:/path/to/opentelemetry-javaagent.jar \ + -Dotel.service.name=producer-svc \ + -Dotel.traces.exporter=otlp \ + -Dotel.metrics.exporter=otlp \ + -Dotel.logs.exporter=otlp \ + -jar /path/to/your/producer.jar +``` + + - update it to the path where you downloaded the Java JAR agent in previous step + - Jar file of your application + +  + +**Note:** +- In case you're dockerising your application, make sure to dockerise it along with OpenTelemetry instrumentation done in previous step. + +  + +If you encounter any difficulties, please consult the [troubleshooting section](https://signoz.io/docs/instrumentation/springboot/#troubleshooting-your-installation) for assistance. \ No newline at end of file diff --git a/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx b/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx index 4cbdb39414..97238b6553 100644 --- a/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx @@ -6,11 +6,15 @@ import { LoadingOutlined, } from '@ant-design/icons'; import logEvent from 'api/common/logEvent'; +import { QueryParams } from 'constants/query'; import Header from 'container/OnboardingContainer/common/Header/Header'; import { useOnboardingContext } from 'container/OnboardingContainer/context/OnboardingContext'; +import { useOnboardingStatus } from 'hooks/messagingQueue / onboarding/useOnboardingStatus'; import { useQueryService } from 'hooks/useQueryService'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; +import useUrlQuery from 'hooks/useUrlQuery'; +import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; @@ -27,6 +31,9 @@ export default function ConnectionStatus(): JSX.Element { GlobalReducer >((state) => state.globalTime); + const urlQuery = useUrlQuery(); + const getStartedSource = urlQuery.get(QueryParams.getStartedSource); + const { serviceName, selectedDataSource, @@ -57,8 +64,65 @@ export default function ConnectionStatus(): JSX.Element { maxTime, selectedTime, selectedTags, + options: { + enabled: getStartedSource !== 'kafka', + }, }); + const [pollInterval, setPollInterval] = useState(10000); + const { + data: onbData, + error: onbErr, + isFetching: onbFetching, + } = useOnboardingStatus({ + enabled: getStartedSource === 'kafka', + refetchInterval: pollInterval, + }); + + const [ + shouldRetryOnboardingCall, + setShouldRetryOnboardingCall, + ] = useState(false); + + useEffect(() => { + // runs only when the caller is coming from 'kafka' i.e. coming from Messaging Queues - setup helper + if (getStartedSource === 'kafka') { + if (onbData?.statusCode !== 200) { + setShouldRetryOnboardingCall(true); + } else if (onbData?.payload?.status === 'success') { + const attributeData = getAttributeDataFromOnboardingStatus( + onbData?.payload, + ); + if (attributeData.overallStatus === 'success') { + setLoading(false); + setIsReceivingData(true); + setPollInterval(false); + } else { + setShouldRetryOnboardingCall(true); + } + } + } + }, [ + shouldRetryOnboardingCall, + onbData, + onbErr, + onbFetching, + getStartedSource, + ]); + + useEffect(() => { + if (retryCount < 0 && getStartedSource === 'kafka') { + setPollInterval(false); + setLoading(false); + } + }, [retryCount, getStartedSource]); + + useEffect(() => { + if (getStartedSource === 'kafka' && !onbFetching) { + setRetryCount((prevCount) => prevCount - 1); + } + }, [getStartedSource, onbData, onbFetching]); + const renderDocsReference = (): JSX.Element => { switch (selectedDataSource?.name) { case 'java': @@ -192,25 +256,27 @@ export default function ConnectionStatus(): JSX.Element { useEffect(() => { let pollingTimer: string | number | NodeJS.Timer | undefined; - if (loading) { - pollingTimer = setInterval(() => { - // Trigger a refetch with the updated parameters - const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000; - const updatedMaxTime = Date.now() * 1000000; + if (getStartedSource !== 'kafka') { + if (loading) { + pollingTimer = setInterval(() => { + // Trigger a refetch with the updated parameters + const updatedMinTime = (Date.now() - 15 * 60 * 1000) * 1000000; + const updatedMaxTime = Date.now() * 1000000; - const payload = { - maxTime: updatedMaxTime, - minTime: updatedMinTime, - selectedTime, - }; + const payload = { + maxTime: updatedMaxTime, + minTime: updatedMinTime, + selectedTime, + }; - dispatch({ - type: UPDATE_TIME_INTERVAL, - payload, - }); - }, pollingInterval); // Same interval as pollingInterval - } else if (!loading && pollingTimer) { - clearInterval(pollingTimer); + dispatch({ + type: UPDATE_TIME_INTERVAL, + payload, + }); + }, pollingInterval); // Same interval as pollingInterval + } else if (!loading && pollingTimer) { + clearInterval(pollingTimer); + } } // Clean up the interval when the component unmounts @@ -221,15 +287,24 @@ export default function ConnectionStatus(): JSX.Element { }, [refetch, selectedTags, selectedTime, loading]); useEffect(() => { - verifyApplicationData(data); + if (getStartedSource !== 'kafka') { + verifyApplicationData(data); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isServiceLoading, data, error, isError]); useEffect(() => { - refetch(); + if (getStartedSource !== 'kafka') { + refetch(); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const isQueryServiceLoading = useMemo( + () => isServiceLoading || loading || onbFetching, + [isServiceLoading, loading, onbFetching], + ); + return (
{renderDocsReference()}
@@ -250,14 +325,14 @@ export default function ConnectionStatus(): JSX.Element {
Status
- {(loading || isServiceLoading) && } - {!(loading || isServiceLoading) && isReceivingData && ( + {isQueryServiceLoading && } + {!isQueryServiceLoading && isReceivingData && ( <> Success )} - {!(loading || isServiceLoading) && !isReceivingData && ( + {!isQueryServiceLoading && !isReceivingData && ( <> Failed @@ -269,11 +344,11 @@ export default function ConnectionStatus(): JSX.Element {
Details
- {(loading || isServiceLoading) &&
Waiting for Update
} - {!(loading || isServiceLoading) && isReceivingData && ( + {isQueryServiceLoading &&
Waiting for Update
} + {!isQueryServiceLoading && isReceivingData && (
Received data from the application successfully.
)} - {!(loading || isServiceLoading) && !isReceivingData && ( + {!isQueryServiceLoading && !isReceivingData && (
Could not detect the install
)}
diff --git a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss index 2bbd18bab2..6b6a032a6b 100644 --- a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss +++ b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.styles.scss @@ -74,4 +74,11 @@ div[class*='-setup-instructions-container'] { .dataSourceName { color: var(--bg-slate-500); } -} \ No newline at end of file +} + +.supported-languages-container { + .disabled { + cursor: not-allowed; + opacity: 0.5; + } +} diff --git a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx index f2f7028bdc..f31fad1aa2 100644 --- a/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/DataSource/DataSource.tsx @@ -6,6 +6,7 @@ import { LoadingOutlined } from '@ant-design/icons'; import { Button, Card, Form, Input, Select, Space, Typography } from 'antd'; import logEvent from 'api/common/logEvent'; 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'; @@ -13,8 +14,10 @@ import { getDataSources, getSupportedFrameworks, hasFrameworks, + messagingQueueKakfaSupportedDataSources, } from 'container/OnboardingContainer/utils/dataSourceUtils'; import { useNotifications } from 'hooks/useNotifications'; +import useUrlQuery from 'hooks/useUrlQuery'; import { Blocks, Check } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -33,6 +36,8 @@ export default function DataSource(): JSX.Element { const { t } = useTranslation(['common']); const history = useHistory(); + const getStartedSource = useUrlQuery().get(QueryParams.getStartedSource); + const { serviceName, selectedModule, @@ -150,13 +155,19 @@ export default function DataSource(): JSX.Element { className={cx( 'supported-language', selectedDataSource?.name === dataSource.name ? 'selected' : '', + getStartedSource === 'kafka' && + !messagingQueueKakfaSupportedDataSources.includes(dataSource?.id || '') + ? 'disabled' + : '', )} key={dataSource.name} onClick={(): void => { - updateSelectedFramework(null); - updateSelectedEnvironment(null); - updateSelectedDataSource(dataSource); - form.setFieldsValue({ selectFramework: null }); + if (getStartedSource !== 'kafka') { + updateSelectedFramework(null); + updateSelectedEnvironment(null); + updateSelectedDataSource(dataSource); + form.setFieldsValue({ selectFramework: null }); + } }} >
diff --git a/frontend/src/container/OnboardingContainer/Steps/MarkdownStep/MarkdownStep.tsx b/frontend/src/container/OnboardingContainer/Steps/MarkdownStep/MarkdownStep.tsx index 6954714342..c6b6e9dbb5 100644 --- a/frontend/src/container/OnboardingContainer/Steps/MarkdownStep/MarkdownStep.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/MarkdownStep/MarkdownStep.tsx @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer'; +import { QueryParams } from 'constants/query'; import { ApmDocFilePaths } from 'container/OnboardingContainer/constants/apmDocFilePaths'; import { AwsMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/awsMonitoringDocFilePaths'; import { AzureMonitoringDocFilePaths } from 'container/OnboardingContainer/constants/azureMonitoringDocFilePaths'; @@ -10,6 +11,7 @@ import { useOnboardingContext, } from 'container/OnboardingContainer/context/OnboardingContext'; import { ModulesMap } from 'container/OnboardingContainer/OnboardingContainer'; +import useUrlQuery from 'hooks/useUrlQuery'; import { useEffect, useState } from 'react'; export interface IngestionInfoProps { @@ -31,6 +33,12 @@ export default function MarkdownStep(): JSX.Element { const [markdownContent, setMarkdownContent] = useState(''); + const urlQuery = useUrlQuery(); + const getStartedSource = urlQuery.get(QueryParams.getStartedSource); + const getStartedSourceService = urlQuery.get( + QueryParams.getStartedSourceService, + ); + const { step } = activeStep; const getFilePath = (): any => { @@ -54,6 +62,12 @@ export default function MarkdownStep(): JSX.Element { path += `_${step?.id}`; + if ( + getStartedSource === 'kafka' && + path === 'APM_java_springBoot_kubernetes_recommendedSteps_runApplication' // todo: Sagar - Make this generic logic in followup PRs + ) { + path += `_${getStartedSourceService}`; + } return path; }; diff --git a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts index b80124b516..df5e296722 100644 --- a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts +++ b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts @@ -252,6 +252,7 @@ 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'; // 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'; @@ -1053,6 +1054,7 @@ 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, // SpringBoot-LinuxAMD64-recommended APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector, diff --git a/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts b/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts index 03f92c2a39..450f17b35a 100644 --- a/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts +++ b/frontend/src/container/OnboardingContainer/utils/dataSourceUtils.ts @@ -399,3 +399,5 @@ export const moduleRouteMap = { [ModulesMap.AwsMonitoring]: ROUTES.GET_STARTED_AWS_MONITORING, [ModulesMap.AzureMonitoring]: ROUTES.GET_STARTED_AZURE_MONITORING, }; + +export const messagingQueueKakfaSupportedDataSources = ['java']; diff --git a/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx b/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx new file mode 100644 index 0000000000..897b0d7e33 --- /dev/null +++ b/frontend/src/hooks/messagingQueue / onboarding/useOnboardingStatus.tsx @@ -0,0 +1,22 @@ +import getOnboardingStatus, { + OnboardingStatusResponse, +} from 'api/messagingQueues/onboarding/getOnboardingStatus'; +import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query'; +import { ErrorResponse, SuccessResponse } from 'types/api'; + +type UseOnboardingStatus = ( + options?: UseQueryOptions< + SuccessResponse | ErrorResponse + >, +) => UseQueryResult | ErrorResponse>; + +export const useOnboardingStatus: UseOnboardingStatus = (options) => + useQuery | ErrorResponse>({ + queryKey: ['onboardingStatus'], + queryFn: () => + getOnboardingStatus({ + start: (Date.now() - 15 * 60 * 1000) * 1_000_000, + end: Date.now() * 1_000_000, + }), + ...options, + }); diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.tsx b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx index ebac021fd2..7819ce092e 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueues.tsx +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx @@ -4,6 +4,7 @@ import { ExclamationCircleFilled } from '@ant-design/icons'; import { Color } from '@signozhq/design-tokens'; import { Button, Modal } 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 { Calendar, ListMinus } from 'lucide-react'; @@ -86,7 +87,7 @@ function MessagingQueues(): JSX.Element { type="default" onClick={(): void => getStartedRedirect( - ROUTES.GET_STARTED_APPLICATION_MONITORING, + `${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=consumer`, 'Configure Consumer', ) } @@ -105,7 +106,7 @@ function MessagingQueues(): JSX.Element { type="default" onClick={(): void => getStartedRedirect( - ROUTES.GET_STARTED_APPLICATION_MONITORING, + `${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=producer`, 'Configure Producer', ) } @@ -124,7 +125,7 @@ function MessagingQueues(): JSX.Element { type="default" onClick={(): void => getStartedRedirect( - ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING, + `${ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=kafka`, 'Monitor kafka', ) } diff --git a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts index 062e4317c2..e75ce5b4e1 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts +++ b/frontend/src/pages/MessagingQueues/MessagingQueuesUtils.ts @@ -1,3 +1,4 @@ +import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus'; import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { GetWidgetQueryBuilderProps } from 'container/MetricsApplication/types'; @@ -344,3 +345,39 @@ export const getMetaDataAndAPIPerView = ( }, }; }; + +interface OnboardingStatusAttributeData { + overallStatus: string; + allAvailableAttributes: string[]; + attributeDataWithError: { attributeName: string; errorMsg: string }[]; +} + +export const getAttributeDataFromOnboardingStatus = ( + onboardingStatus?: OnboardingStatusResponse | null, +): OnboardingStatusAttributeData => { + const allAvailableAttributes: string[] = []; + const attributeDataWithError: { + attributeName: string; + errorMsg: string; + }[] = []; + + if (onboardingStatus?.data && !isEmpty(onboardingStatus?.data)) { + onboardingStatus.data.forEach((status) => { + if (status.attribute) { + allAvailableAttributes.push(status.attribute); + if (status.status === '0') { + attributeDataWithError.push({ + attributeName: status.attribute, + errorMsg: status.error_message || '', + }); + } + } + }); + } + + return { + overallStatus: attributeDataWithError.length ? 'error' : 'success', + allAvailableAttributes, + attributeDataWithError, + }; +}; From 352296c6cd1f5437a9a4fc6b882411fab9e5ed42 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Tue, 5 Nov 2024 22:13:12 +0530 Subject: [PATCH 37/52] fix: initialize target to 3 in anomaly detection alert (#6362) --- frontend/src/container/CreateAlertRule/defaults.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/container/CreateAlertRule/defaults.ts b/frontend/src/container/CreateAlertRule/defaults.ts index 44dee01d31..34058a06f6 100644 --- a/frontend/src/container/CreateAlertRule/defaults.ts +++ b/frontend/src/container/CreateAlertRule/defaults.ts @@ -94,6 +94,7 @@ export const anamolyAlertDefaults: AlertDef = { matchType: defaultMatchType, algorithm: defaultAlgorithm, seasonality: defaultSeasonality, + target: 3, }, labels: { severity: 'warning', From 7086470ce2992f3397b90e611e62202a07e60e20 Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:23:51 +0530 Subject: [PATCH 38/52] feat: added healthcheck and attribute checklist component for Kafka (#6371) * feat: added healthcheck and attribute checklist component for Kafka * feat: corrected the onboardingapi payload * feat: added missing configuration button at overview and onboarding flow --- .../onboarding/getOnboardingStatus.ts | 6 +- ...ot-kubernetes-runApplication-producers.md} | 0 .../ConnectionStatus/ConnectionStatus.tsx | 52 +++-- .../Steps/DataSource/DataSource.tsx | 12 +- .../constants/apmDocFilePaths.ts | 4 +- .../ onboarding/useOnboardingStatus.tsx | 11 +- .../AttributeCheckList.tsx | 206 ++++++++++++++++++ .../MessagingQueueHealthCheck.styles.scss | 165 ++++++++++++++ .../MessagingQueueHealthCheck.tsx | 133 +++++++++++ .../MessagingQueues.styles.scss | 30 ++- .../pages/MessagingQueues/MessagingQueues.tsx | 19 +- .../MessagingQueues/MessagingQueuesUtils.ts | 6 + 12 files changed, 603 insertions(+), 41 deletions(-) rename frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/{springBoot-kubernetes-runApplication-producer.md => springBoot-kubernetes-runApplication-producers.md} (100%) create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.styles.scss create mode 100644 frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.tsx diff --git a/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts b/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts index b5da83aa13..da82e70134 100644 --- a/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts +++ b/frontend/src/api/messagingQueues/onboarding/getOnboardingStatus.ts @@ -16,11 +16,13 @@ export interface OnboardingStatusResponse { const getOnboardingStatus = async (props: { start: number; end: number; + endpointService?: string; }): Promise | ErrorResponse> => { + const { endpointService, ...rest } = props; try { const response = await ApiBaseInstance.post( - '/messaging-queues/kafka/onboarding/consumers', - props, + `/messaging-queues/kafka/onboarding/${endpointService || 'consumers'}`, + rest, ); return { diff --git a/frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md b/frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producers.md similarity index 100% rename from frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producer.md rename to frontend/src/container/OnboardingContainer/Modules/APM/Java/md-docs/SpringBoot/Kubernetes/springBoot-kubernetes-runApplication-producers.md diff --git a/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx b/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx index 97238b6553..0fc81c0533 100644 --- a/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx +++ b/frontend/src/container/OnboardingContainer/Steps/ConnectionStatus/ConnectionStatus.tsx @@ -14,6 +14,7 @@ import { useQueryService } from 'hooks/useQueryService'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; import useUrlQuery from 'hooks/useUrlQuery'; +import MessagingQueueHealthCheck from 'pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck'; import { getAttributeDataFromOnboardingStatus } from 'pages/MessagingQueues/MessagingQueuesUtils'; import { useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; @@ -33,6 +34,9 @@ export default function ConnectionStatus(): JSX.Element { const urlQuery = useUrlQuery(); const getStartedSource = urlQuery.get(QueryParams.getStartedSource); + const getStartedSourceService = urlQuery.get( + QueryParams.getStartedSourceService, + ); const { serviceName, @@ -74,10 +78,14 @@ export default function ConnectionStatus(): JSX.Element { data: onbData, error: onbErr, isFetching: onbFetching, - } = useOnboardingStatus({ - enabled: getStartedSource === 'kafka', - refetchInterval: pollInterval, - }); + } = useOnboardingStatus( + { + enabled: getStartedSource === 'kafka', + refetchInterval: pollInterval, + }, + getStartedSourceService || '', + 'query-key-onboarding-status', + ); const [ shouldRetryOnboardingCall, @@ -326,18 +334,30 @@ export default function ConnectionStatus(): JSX.Element {
{isQueryServiceLoading && } - {!isQueryServiceLoading && isReceivingData && ( - <> - - Success - - )} - {!isQueryServiceLoading && !isReceivingData && ( - <> - - Failed - - )} + {!isQueryServiceLoading && + isReceivingData && + (getStartedSource !== 'kafka' ? ( + <> + + Success + + ) : ( + + ))} + {!isQueryServiceLoading && + !isReceivingData && + (getStartedSource !== 'kafka' ? ( + <> + + Failed + + ) : ( + + ))}
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..1e6909fca7 100644 --- a/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts +++ b/frontend/src/container/OnboardingContainer/constants/apmDocFilePaths.ts @@ -252,7 +252,7 @@ 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_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 +1054,7 @@ 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, // SpringBoot-LinuxAMD64-recommended APM_java_springBoot_linuxAMD64_recommendedSteps_setupOtelCollector, 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/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx new file mode 100644 index 0000000000..88e2147a0e --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx @@ -0,0 +1,206 @@ +import './MessagingQueueHealthCheck.styles.scss'; + +import { CaretDownOutlined, LoadingOutlined } from '@ant-design/icons'; +import { + Modal, + Select, + Spin, + Tooltip, + Tree, + TreeDataNode, + Typography, +} from 'antd'; +import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus'; +import { Bolt, Check, OctagonAlert, X } from 'lucide-react'; +import { ReactNode, useEffect, useState } from 'react'; +import { v4 as uuid } from 'uuid'; + +interface AttributeCheckListProps { + visible: boolean; + onClose: () => void; + onboardingStatusResponses: { + title: string; + data: OnboardingStatusResponse['data']; + errorMsg?: string; + }[]; + loading: boolean; +} + +export enum AttributesFilters { + ALL = 'all', + SUCCESS = 'success', + ERROR = 'error', +} + +function ErrorTitleAndKey({ + title, + errorMsg, + isLeaf, +}: { + title: string; + errorMsg?: string; + isLeaf?: boolean; +}): TreeDataNode { + return { + key: `${title}-key-${uuid()}`, + title: ( +
+ + {title} + + +
+ + Fix +
+
+
+ ), + isLeaf, + }; +} + +function AttributeLabels({ title }: { title: ReactNode }): JSX.Element { + return ( +
+ + {title} +
+ ); +} + +function treeTitleAndKey({ + title, + isLeaf, +}: { + title: string; + isLeaf?: boolean; +}): TreeDataNode { + return { + key: `${title}-key-${uuid()}`, + title: ( +
+ + {title} + + {isLeaf && ( +
+ + + +
+ )} +
+ ), + isLeaf, + }; +} + +function generateTreeDataNodes( + response: OnboardingStatusResponse['data'], +): TreeDataNode[] { + return response + .map((item) => { + if (item.attribute) { + if (item.status === '1') { + return treeTitleAndKey({ title: item.attribute, isLeaf: true }); + } + if (item.status === '0') { + return ErrorTitleAndKey({ + title: item.attribute, + errorMsg: item.error_message || '', + }); + } + } + return null; + }) + .filter(Boolean) as TreeDataNode[]; +} + +function AttributeCheckList({ + visible, + onClose, + onboardingStatusResponses, + loading, +}: AttributeCheckListProps): JSX.Element { + const [filter, setFilter] = useState(AttributesFilters.ALL); + const [treeData, setTreeData] = useState([]); + + const handleFilterChange = (value: AttributesFilters): void => { + setFilter(value); + }; + + useEffect(() => { + const filteredData = onboardingStatusResponses.map((response) => { + if (response.errorMsg) { + return ErrorTitleAndKey({ + title: response.title, + errorMsg: response.errorMsg, + isLeaf: true, + }); + } + let filteredData = response.data; + + if (filter === AttributesFilters.SUCCESS) { + filteredData = response.data.filter((item) => item.status === '1'); + } else if (filter === AttributesFilters.ERROR) { + filteredData = response.data.filter((item) => item.status === '0'); + } + + return { + ...treeTitleAndKey({ title: response.title }), + children: generateTreeDataNodes(filteredData), + }; + }); + + setTreeData(filteredData); + }, [filter, onboardingStatusResponses]); + + return ( + } + > + {loading ? ( +
+ } size="large" /> +
+ ) : ( +
+
+ + ); +} + +export default DropRateView; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/EvaluationTimeSelector.tsx b/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/EvaluationTimeSelector.tsx new file mode 100644 index 0000000000..2ca2e9c301 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/EvaluationTimeSelector.tsx @@ -0,0 +1,111 @@ +import './DropRateView.styles.scss'; + +import { Input, Select, Typography } from 'antd'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; + +const { Option } = Select; + +interface SelectDropdownRenderProps { + menu: React.ReactNode; + inputValue: string; + handleInputChange: (e: React.ChangeEvent) => void; + handleKeyDown: (e: React.KeyboardEvent) => void; + handleAddCustomValue: () => void; +} + +function SelectDropdownRender({ + menu, + inputValue, + handleInputChange, + handleAddCustomValue, + handleKeyDown, +}: SelectDropdownRenderProps): JSX.Element { + return ( + <> + {menu} + + + ); +} + +function EvaluationTimeSelector({ + setInterval, +}: { + setInterval: Dispatch>; +}): JSX.Element { + const [inputValue, setInputValue] = useState(''); + const [selectedInterval, setSelectedInterval] = useState('5ms'); + const [dropdownOpen, setDropdownOpen] = useState(false); + + const handleInputChange = (e: React.ChangeEvent): void => { + setInputValue(e.target.value); + }; + + const handleSelectChange = (value: string): void => { + setSelectedInterval(value); + setInputValue(''); + setDropdownOpen(false); + }; + + const handleAddCustomValue = (): void => { + setSelectedInterval(inputValue); + setInputValue(inputValue); + setDropdownOpen(false); + }; + + const handleKeyDown = (e: React.KeyboardEvent): void => { + if (e.key === 'Enter') { + e.preventDefault(); + e.stopPropagation(); + handleAddCustomValue(); + } + }; + + const renderDropdown = (menu: React.ReactNode): JSX.Element => ( + + ); + + useEffect(() => { + if (selectedInterval) { + setInterval(() => selectedInterval); + } + }, [selectedInterval, setInterval]); + + return ( +
+ + Evaluation Interval: + + +
+ ); +} + +export default EvaluationTimeSelector; diff --git a/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/dropRateViewUtils.ts b/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/dropRateViewUtils.ts new file mode 100644 index 0000000000..49d751e722 --- /dev/null +++ b/frontend/src/pages/MessagingQueues/MQDetails/DropRateView/dropRateViewUtils.ts @@ -0,0 +1,46 @@ +export function convertToMilliseconds(timeInput: string): number { + if (!timeInput.trim()) { + return 0; + } + + const match = timeInput.match(/^(\d+)(ms|s|ns)?$/); // Match number and optional unit + if (!match) { + throw new Error(`Invalid time format: ${timeInput}`); + } + + const value = parseInt(match[1], 10); + const unit = match[2] || 'ms'; // Default to 'ms' if no unit is provided + + switch (unit) { + case 's': + return value * 1e3; + case 'ms': + return value; + case 'ns': + return value / 1e6; + default: + throw new Error('Invalid time format'); + } +} + +export interface DropRateResponse { + timestamp: string; + data: { + breach_percentage: number; + breached_spans: number; + consumer_service: string; + producer_service: string; + top_traceIDs: string[]; + total_spans: number; + }; +} +export interface DropRateAPIResponse { + status: string; + data: { + resultType: string; + result: { + queryName: string; + list: DropRateResponse[]; + }[]; + }; +} diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss index c4995a1812..5a746bbcae 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.style.scss @@ -17,13 +17,20 @@ background: var(--bg-ink-500); .mq-overview-title { - color: var(--bg-vanilla-200); + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; - font-family: Inter; - font-size: 18px; - font-style: normal; - font-weight: 500; - line-height: 28px; + .drop-rat-title { + color: var(--bg-vanilla-200); + + font-family: Inter; + font-size: 18px; + font-style: normal; + font-weight: 500; + line-height: 28px; + } } .mq-details-options { @@ -43,3 +50,69 @@ } } } + +.droprate-view { + .mq-table { + width: 100%; + + .ant-table-content { + border-radius: 6px; + border: 1px solid var(--bg-slate-500); + box-shadow: 0px 4px 12px 0px rgba(0, 0, 0, 0.1); + } + + .ant-table-tbody { + .ant-table-cell { + max-width: 250px; + border-bottom: none; + } + } + + .ant-table-thead { + .ant-table-cell { + background-color: var(--bg-ink-500); + border-bottom: 1px solid var(--bg-slate-500); + } + } + } + + .trace-id-list { + display: flex; + flex-direction: column; + gap: 4px; + width: max-content; + + .traceid-style { + display: flex; + gap: 8px; + align-items: center; + + .traceid-text { + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-slate-400); + padding: 2px; + cursor: pointer; + } + + .remaing-count { + cursor: pointer; + color: var(--bg-vanilla-100); + font-family: Inter; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: normal; + letter-spacing: -0.06px; + } + } + } +} + +.pagination-left { + &.mq-table { + .ant-pagination { + justify-content: flex-start; + } + } +} diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx index 34ab160553..e1e1791f32 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx @@ -249,7 +249,7 @@ function MessagingQueuesTable({
, -): Promise< - SuccessResponse | ErrorResponse -> => { +): Promise | ErrorResponse> => { const { start, end, evalTime } = props; const response = await axios.post(`messaging-queues/kafka/span/evaluation`, { start, From fdc54a62a90348143629b3376da0a9bd60a6c3ad Mon Sep 17 00:00:00 2001 From: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:49:47 +0530 Subject: [PATCH 49/52] fix: kafka - misc fix and features (#6379) * feat: fixed multiple fixes and chores in kafka 2.0 * feat: fixed producer latency - producer-detail call * feat: fixed mq-detail page layout and pagination * feat: resolved comments --- .../MessagingQueues/MQDetails/MQDetails.tsx | 7 +- .../MQDetails/MQTables/MQTables.tsx | 6 +- .../AttributeCheckList.tsx | 68 ++++++++++++++++++- .../MessagingQueueHealthCheck.styles.scss | 3 + .../MessagingQueues.styles.scss | 28 +++----- .../pages/MessagingQueues/MessagingQueues.tsx | 4 +- 6 files changed, 87 insertions(+), 29 deletions(-) diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx index 40a943172f..3609b2d226 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQDetails.tsx @@ -116,12 +116,7 @@ const checkValidityOfDetailConfigs = ( return false; } - if (currentTab === MessagingQueueServiceDetailType.ConsumerDetails) { - return Boolean(configDetails?.topic && configDetails?.partition); - } - return Boolean( - configDetails?.group && configDetails?.topic && configDetails?.partition, - ); + return Boolean(configDetails?.topic && configDetails?.partition); } if (selectedView === MessagingQueuesViewType.producerLatency.value) { diff --git a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx index e1e1791f32..73fd1b2f41 100644 --- a/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx +++ b/frontend/src/pages/MessagingQueues/MQDetails/MQTables/MQTables.tsx @@ -33,6 +33,8 @@ import { MessagingQueuesPayloadProps, } from './getConsumerLagDetails'; +const INITIAL_PAGE_SIZE = 10; + // eslint-disable-next-line sonarjs/cognitive-complexity export function getColumns( data: MessagingQueuesPayloadProps['payload'], @@ -155,8 +157,8 @@ function MessagingQueuesTable({ const paginationConfig = useMemo( () => - tableData?.length > 20 && { - pageSize: 20, + tableData?.length > INITIAL_PAGE_SIZE && { + pageSize: INITIAL_PAGE_SIZE, showTotal: showPaginationItem, showSizeChanger: false, hideOnSinglePage: true, diff --git a/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx index 88e2147a0e..08b2ce6cfa 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx +++ b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/AttributeCheckList.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import './MessagingQueueHealthCheck.styles.scss'; import { CaretDownOutlined, LoadingOutlined } from '@ant-design/icons'; @@ -11,10 +13,20 @@ import { Typography, } from 'antd'; import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus'; +import { QueryParams } from 'constants/query'; +import ROUTES from 'constants/routes'; +import { History } from 'history'; import { Bolt, Check, OctagonAlert, X } from 'lucide-react'; import { ReactNode, useEffect, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { isCloudUser } from 'utils/app'; import { v4 as uuid } from 'uuid'; +import { + KAFKA_SETUP_DOC_LINK, + MessagingQueueHealthCheckService, +} from '../MessagingQueuesUtils'; + interface AttributeCheckListProps { visible: boolean; onClose: () => void; @@ -34,13 +46,42 @@ export enum AttributesFilters { function ErrorTitleAndKey({ title, + parentTitle, + history, + isCloudUserVal, errorMsg, isLeaf, }: { title: string; + parentTitle: string; + isCloudUserVal: boolean; + history: History; errorMsg?: string; isLeaf?: boolean; }): TreeDataNode { + const handleRedirection = (): void => { + let link = ''; + + switch (parentTitle) { + case 'Consumers': + link = `${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Consumers}`; + break; + case 'Producers': + link = `${ROUTES.GET_STARTED_APPLICATION_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Producers}`; + break; + case 'Kafka': + link = `${ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING}?${QueryParams.getStartedSource}=kafka&${QueryParams.getStartedSourceService}=${MessagingQueueHealthCheckService.Kafka}`; + break; + default: + link = ''; + } + + if (isCloudUserVal && !!link) { + history.push(link); + } else { + window.open(KAFKA_SETUP_DOC_LINK, '_blank'); + } + }; return { key: `${title}-key-${uuid()}`, title: ( @@ -49,7 +90,13 @@ function ErrorTitleAndKey({ {title} -
+
{ + e.preventDefault(); + handleRedirection(); + }} + > Fix
@@ -98,6 +145,9 @@ function treeTitleAndKey({ function generateTreeDataNodes( response: OnboardingStatusResponse['data'], + parentTitle: string, + isCloudUserVal: boolean, + history: History, ): TreeDataNode[] { return response .map((item) => { @@ -109,6 +159,9 @@ function generateTreeDataNodes( return ErrorTitleAndKey({ title: item.attribute, errorMsg: item.error_message || '', + parentTitle, + history, + isCloudUserVal, }); } } @@ -129,6 +182,8 @@ function AttributeCheckList({ const handleFilterChange = (value: AttributesFilters): void => { setFilter(value); }; + const isCloudUserVal = isCloudUser(); + const history = useHistory(); useEffect(() => { const filteredData = onboardingStatusResponses.map((response) => { @@ -137,6 +192,9 @@ function AttributeCheckList({ title: response.title, errorMsg: response.errorMsg, isLeaf: true, + parentTitle: response.title, + history, + isCloudUserVal, }); } let filteredData = response.data; @@ -149,11 +207,17 @@ function AttributeCheckList({ return { ...treeTitleAndKey({ title: response.title }), - children: generateTreeDataNodes(filteredData), + children: generateTreeDataNodes( + filteredData, + response.title, + isCloudUserVal, + history, + ), }; }); setTreeData(filteredData); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [filter, onboardingStatusResponses]); return ( diff --git a/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.styles.scss b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.styles.scss index 00b3ad8df8..22a1bed584 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.styles.scss +++ b/frontend/src/pages/MessagingQueues/MessagingQueueHealthCheck/MessagingQueueHealthCheck.styles.scss @@ -68,6 +68,8 @@ .ant-tree { .ant-tree-title { + cursor: default; + .attribute-error-title { display: flex; align-items: center; @@ -88,6 +90,7 @@ font-style: normal; font-weight: 400; line-height: 16px; + cursor: pointer; } } diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss index bcfd62c773..9959bebe26 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.styles.scss @@ -45,28 +45,22 @@ border-bottom: 1px solid var(--bg-slate-500); - .header-content { + .header-config { display: flex; gap: 12px; align-items: center; - .header-config { - display: flex; - gap: 10px; - align-items: center; + .messaging-queue-options { + .ant-select-selector { + display: flex; + height: 32px; + padding: 6px 6px 6px 8px; + align-items: center; + gap: 4px; - .messaging-queue-options { - .ant-select-selector { - display: flex; - height: 32px; - padding: 6px 6px 6px 8px; - align-items: center; - gap: 4px; - - border-radius: 2px; - border: 1px solid var(--bg-slate-400); - background: var(--bg-ink-300); - } + border-radius: 2px; + border: 1px solid var(--bg-slate-400); + background: var(--bg-ink-300); } } } diff --git a/frontend/src/pages/MessagingQueues/MessagingQueues.tsx b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx index 9e2c630bb0..34063fc3b8 100644 --- a/frontend/src/pages/MessagingQueues/MessagingQueues.tsx +++ b/frontend/src/pages/MessagingQueues/MessagingQueues.tsx @@ -60,8 +60,8 @@ function MessagingQueues(): JSX.Element { {t('breadcrumb')}
-
-
{t('header')}
+
+ {t('header')} / Date: Fri, 8 Nov 2024 12:22:39 +0530 Subject: [PATCH 50/52] chore: update events for onboarding part 2 (#6397) --- .../AboutSigNozQuestions.tsx | 2 +- .../InviteTeamMembers/InviteTeamMembers.tsx | 51 ++++++++------- .../OptimiseSignozNeeds.tsx | 8 +-- .../OrgQuestions/OrgQuestions.tsx | 18 +++++- .../OnboardingQuestionaire/index.tsx | 62 +++++++++++++++++-- 5 files changed, 106 insertions(+), 35 deletions(-) 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, - }); + 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, + }); - handleInviteUsersSuccess(response); + 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} /> )} From 22c10f94795da2f37f702c747ef07334906859b5 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Fri, 8 Nov 2024 12:35:32 +0530 Subject: [PATCH 51/52] Issue 6367 (#6376) * fix: issue with orderby by materialized column * fix: tests * fix: order by issue in old explorer as well --- pkg/query-service/app/logs/v3/query_builder.go | 3 --- pkg/query-service/app/logs/v3/query_builder_test.go | 8 ++++---- pkg/query-service/app/logs/v4/query_builder.go | 3 --- pkg/query-service/app/logs/v4/query_builder_test.go | 10 ++++++---- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index e0c88e3ac1..8f14fea99d 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -419,9 +419,6 @@ func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]st } else if panelType == v3.PanelTypeList { attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} name := getClickhouseColumnName(attr) - if item.IsColumn { - name = "`" + name + "`" - } orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) } } diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 3191820dbb..958a3fa93f 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -788,14 +788,14 @@ var testBuildLogsQueryData = []struct { AggregateOperator: v3.AggregateOperatorNoOp, Expression: "A", Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, - OrderBy: []v3.OrderBy{{ColumnName: "method", DataType: v3.AttributeKeyDataTypeString, Order: "ASC", IsColumn: true}}, + OrderBy: []v3.OrderBy{{ColumnName: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag, Order: "ASC", IsColumn: true}}, }, ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string," + "CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64," + "CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool," + "CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string," + "CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope " + - "from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by `method` ASC", + "from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by `attribute_string_method` ASC", }, { Name: "Test Noop with filter", @@ -1524,7 +1524,7 @@ var testPrepLogsQueryLimitOffsetData = []struct { PageSize: 5, }, TableName: "logs", - ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string,CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by `timestamp` desc LIMIT 1", + ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string,CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) order by timestamp desc LIMIT 1", }, { Name: "Test limit greater than pageSize - order by ts", @@ -1545,7 +1545,7 @@ var testPrepLogsQueryLimitOffsetData = []struct { PageSize: 10, }, TableName: "logs", - ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string,CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by `timestamp` desc LIMIT 10", + ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((attributes_bool_key, attributes_bool_value), 'Map(String, Bool)') as attributes_bool,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string,CAST((scope_string_key, scope_string_value), 'Map(String, String)') as scope from signoz_logs.distributed_logs where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by timestamp desc LIMIT 10", }, { Name: "Test limit less than pageSize - order by custom", diff --git a/pkg/query-service/app/logs/v4/query_builder.go b/pkg/query-service/app/logs/v4/query_builder.go index 3952d0e7e1..42cb19befc 100644 --- a/pkg/query-service/app/logs/v4/query_builder.go +++ b/pkg/query-service/app/logs/v4/query_builder.go @@ -255,9 +255,6 @@ func orderBy(panelType v3.PanelType, items []v3.OrderBy, tagLookup map[string]st } else if panelType == v3.PanelTypeList { attr := v3.AttributeKey{Key: item.ColumnName, DataType: item.DataType, Type: item.Type, IsColumn: item.IsColumn} name := getClickhouseKey(attr) - if item.IsColumn { - name = "`" + name + "`" - } orderBy = append(orderBy, fmt.Sprintf("%s %s", name, item.Order)) } } diff --git a/pkg/query-service/app/logs/v4/query_builder_test.go b/pkg/query-service/app/logs/v4/query_builder_test.go index cbc9a450f3..4ce3721e18 100644 --- a/pkg/query-service/app/logs/v4/query_builder_test.go +++ b/pkg/query-service/app/logs/v4/query_builder_test.go @@ -520,14 +520,16 @@ func Test_orderByAttributeKeyTags(t *testing.T) { { ColumnName: "bytes", Order: "asc", + IsColumn: true, + Type: v3.AttributeKeyTypeTag, + DataType: v3.AttributeKeyDataTypeString, }, }, tags: []v3.AttributeKey{ {Key: "name"}, - {Key: "bytes"}, }, }, - want: "`name` asc,value asc,`bytes` asc", + want: "`name` asc,value asc,`attribute_string_bytes` asc", }, { name: "test 4", @@ -1016,7 +1018,7 @@ func TestPrepareLogsQuery(t *testing.T) { }, want: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string from " + "signoz_logs.distributed_logs_v2 where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " + - "order by `timestamp` desc LIMIT 1", + "order by timestamp desc LIMIT 1", }, { name: "Test limit greater than pageSize - order by ts", @@ -1041,7 +1043,7 @@ func TestPrepareLogsQuery(t *testing.T) { }, want: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string from " + "signoz_logs.distributed_logs_v2 where (timestamp >= 1680066360726000000 AND timestamp <= 1680066458000000000) AND (ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) " + - "AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by `timestamp` desc LIMIT 10", + "AND id < '2TNh4vp2TpiWyLt3SzuadLJF2s4' order by timestamp desc LIMIT 10", }, { name: "Test limit less than pageSize - order by custom", From 48f3b9cacb75ccf542df3380c23abf1e6b63124f Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Fri, 8 Nov 2024 16:02:59 +0530 Subject: [PATCH 52/52] chore(signoz): pin versions: SigNoz 0.58.0, SigNoz OtelCollector 0.111.8 Signed-off-by: Prashant Shahi --- .../docker-swarm/clickhouse-setup/docker-compose.yaml | 8 ++++---- .../docker/clickhouse-setup/docker-compose-core.yaml | 4 ++-- .../clickhouse-setup/docker-compose-minimal.yaml | 10 +++++----- .../clickhouse-setup/docker-compose.testing.yaml | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 8031f65547..96cdcd81d1 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -146,7 +146,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.57.0 + image: signoz/query-service:0.58.0 command: [ "-config=/root/config/prometheus.yml", @@ -186,7 +186,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:0.57.0 + image: signoz/frontend:0.58.0 deploy: restart_policy: condition: on-failure @@ -199,7 +199,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.111.5 + image: signoz/signoz-otel-collector:0.111.8 command: [ "--config=/etc/otel-collector-config.yaml", @@ -237,7 +237,7 @@ services: - query-service otel-collector-migrator: - image: signoz/signoz-schema-migrator:0.111.5 + image: signoz/signoz-schema-migrator:0.111.8 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index ec9e0697fb..529b3bebd3 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -69,7 +69,7 @@ services: - --storage.path=/data otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -84,7 +84,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: signoz-otel-collector - image: signoz/signoz-otel-collector:0.111.5 + image: signoz/signoz-otel-collector:0.111.8 command: [ "--config=/etc/otel-collector-config.yaml", diff --git a/deploy/docker/clickhouse-setup/docker-compose-minimal.yaml b/deploy/docker/clickhouse-setup/docker-compose-minimal.yaml index 6b9b96b331..19ab6065f9 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-minimal.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-minimal.yaml @@ -162,7 +162,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.57.0} + image: signoz/query-service:${DOCKER_TAG:-0.58.0} container_name: signoz-query-service command: [ @@ -201,7 +201,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.57.0} + image: signoz/frontend:${DOCKER_TAG:-0.58.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -213,7 +213,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator-sync: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8} container_name: otel-migrator-sync command: - "sync" @@ -228,7 +228,7 @@ services: # condition: service_healthy otel-collector-migrator-async: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8} container_name: otel-migrator-async command: - "async" @@ -245,7 +245,7 @@ services: # condition: service_healthy otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.8} container_name: signoz-otel-collector command: [ diff --git a/deploy/docker/clickhouse-setup/docker-compose.testing.yaml b/deploy/docker/clickhouse-setup/docker-compose.testing.yaml index 3bdbc2cae4..fc2ef0ed3c 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.testing.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.testing.yaml @@ -167,7 +167,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.57.0} + image: signoz/query-service:${DOCKER_TAG:-0.58.0} container_name: signoz-query-service command: [ @@ -208,7 +208,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.57.0} + image: signoz/frontend:${DOCKER_TAG:-0.58.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -220,7 +220,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.8} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -234,7 +234,7 @@ services: otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.8} container_name: signoz-otel-collector command: [ diff --git a/go.mod b/go.mod index e211f2692c..c231b4b196 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/ClickHouse/clickhouse-go/v2 v2.25.0 github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd - github.com/SigNoz/signoz-otel-collector v0.111.5 + github.com/SigNoz/signoz-otel-collector v0.111.8 github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 github.com/antonmedv/expr v1.15.3 diff --git a/go.sum b/go.sum index a09bb6b322..7eb05b39f0 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd h1:Bk43AsDYe0fhkb github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd/go.mod h1:nxRcH/OEdM8QxzH37xkGzomr1O0JpYBRS6pwjsWW6Pc= github.com/SigNoz/prometheus v1.12.0 h1:+BXeIHyMOOWWa+xjhJ+x80JFva7r1WzWIfIhQ5PUmIE= github.com/SigNoz/prometheus v1.12.0/go.mod h1:EqNM27OwmPfqMUk+E+XG1L9rfDFcyXnzzDrg0EPOfxA= -github.com/SigNoz/signoz-otel-collector v0.111.5 h1:kLpJSv9U46doA+89nfUvTLcNb6WbIxiMAtNlTNL88ZE= -github.com/SigNoz/signoz-otel-collector v0.111.5/go.mod h1:/nyVFDiEz/QBfyqekB3zRwstZ/KSIB85qgV9NnzAtig= +github.com/SigNoz/signoz-otel-collector v0.111.8 h1:t3V3Ahue2ucryRdHvqz33zRCPGQ86xkAsx9J23ZNPk0= +github.com/SigNoz/signoz-otel-collector v0.111.8/go.mod h1:/nyVFDiEz/QBfyqekB3zRwstZ/KSIB85qgV9NnzAtig= github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc= github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo= github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY=