From c5b3b7c1e58665742c4d53381422640a6ca96f41 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 20 Jan 2025 15:17:33 +0100 Subject: [PATCH 1/7] Add Google sign in --- package-lock.json | 11 +++++ package.json | 1 + .../Login/GoogleSignInButton/index.tsx | 34 ++++++++++++++ .../Login/GoogleSignInButton/styles.ts | 20 ++++++++ .../Login/GoogleSignInButton/types.ts | 11 +++++ src/components/Login/index.tsx | 47 ++++++++++++++++++- src/components/Login/styles.ts | 21 ++++++++- src/components/common/App/ConfigContext.ts | 3 +- src/components/common/App/types.ts | 1 + .../common/icons/20px/GoogleLogoIcon.tsx | 44 +++++++++++++++++ src/components/common/v3/TextField/index.tsx | 4 +- src/components/common/v3/TextField/types.ts | 2 + src/containers/Login/GoogleAuthHoC/index.tsx | 18 +++++++ src/containers/Login/GoogleAuthHoC/types.ts | 5 ++ src/containers/Login/index.tsx | 5 +- src/globals.d.ts | 1 + 16 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 src/components/Login/GoogleSignInButton/index.tsx create mode 100644 src/components/Login/GoogleSignInButton/styles.ts create mode 100644 src/components/Login/GoogleSignInButton/types.ts create mode 100644 src/components/common/icons/20px/GoogleLogoIcon.tsx create mode 100644 src/containers/Login/GoogleAuthHoC/index.tsx create mode 100644 src/containers/Login/GoogleAuthHoC/types.ts diff --git a/package-lock.json b/package-lock.json index 0828d5d12..7b2fef226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@floating-ui/react": "^0.25.1", "@formkit/auto-animate": "^0.8.2", + "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.7.8", "allotment": "^1.19.0", @@ -4153,6 +4154,16 @@ "@octokit/openapi-types": "^22.2.0" } }, + "node_modules/@react-oauth/google": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.1.tgz", + "integrity": "sha512-qagsy22t+7UdkYAiT5ZhfM4StXi9PPNvw0zuwNmabrWyMKddczMtBIOARflbaIj+wHiQjnMAsZmzsUYuXeyoSg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz", diff --git a/package.json b/package.json index 9b1726946..b9b29dd9f 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "dependencies": { "@floating-ui/react": "^0.25.1", "@formkit/auto-animate": "^0.8.2", + "@react-oauth/google": "^0.12.1", "@reduxjs/toolkit": "^2.5.0", "@tanstack/react-table": "^8.7.8", "allotment": "^1.19.0", diff --git a/src/components/Login/GoogleSignInButton/index.tsx b/src/components/Login/GoogleSignInButton/index.tsx new file mode 100644 index 000000000..6eecf766a --- /dev/null +++ b/src/components/Login/GoogleSignInButton/index.tsx @@ -0,0 +1,34 @@ +import { useGoogleLogin } from "@react-oauth/google"; +import { GoogleLogoIcon } from "../../common/icons/20px/GoogleLogoIcon"; +import * as s from "./styles"; +import type { GoogleSignInButtonProps } from "./types"; + +export const GoogleSignInButton = ({ + onSuccess, + onError, + onNonOAuthError +}: GoogleSignInButtonProps) => { + const googleLogin = useGoogleLogin({ + flow: "auth-code", + onSuccess: (response) => { + onSuccess(response); + }, + onError: (error) => { + onError(error); + }, + onNonOAuthError: (error) => { + onNonOAuthError(error); + } + }); + + const handleClick = () => { + googleLogin(); + }; + + return ( + + + Sign in with Google + + ); +}; diff --git a/src/components/Login/GoogleSignInButton/styles.ts b/src/components/Login/GoogleSignInButton/styles.ts new file mode 100644 index 000000000..476463a53 --- /dev/null +++ b/src/components/Login/GoogleSignInButton/styles.ts @@ -0,0 +1,20 @@ +import styled from "styled-components"; +import { getMainFont } from "../../common/App/styles"; + +export const Button = styled.button` + ${getMainFont("Roboto")} + + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + padding: 10px 12px; + border-radius: 4px; + border: 1px solid #747775; + background: #fff; + color: #1f1f1f; + font-size: 14px; + font-weight: 500; + line-height: 20px; + cursor: pointer; +`; diff --git a/src/components/Login/GoogleSignInButton/types.ts b/src/components/Login/GoogleSignInButton/types.ts new file mode 100644 index 000000000..ed624a52f --- /dev/null +++ b/src/components/Login/GoogleSignInButton/types.ts @@ -0,0 +1,11 @@ +import type { CodeResponse, NonOAuthError } from "@react-oauth/google"; + +export interface GoogleSignInButtonProps { + onSuccess: ( + response: Omit + ) => void; + onError: ( + response: Pick + ) => void; + onNonOAuthError: (error: NonOAuthError) => void; +} diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index be491f4e8..32d1ac9c4 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -1,8 +1,11 @@ +import type { CodeResponse } from "@react-oauth/google"; import axios, { isAxiosError } from "axios"; import type { ChangeEvent, FormEvent } from "react"; import { useState } from "react"; import { Helmet } from "react-helmet"; +import { isString } from "../../typeGuards/isString"; import { TextField } from "../common/v3/TextField"; +import { GoogleSignInButton } from "./GoogleSignInButton"; import * as s from "./styles"; export const Login = () => { @@ -34,7 +37,7 @@ export const Login = () => { } }) .catch((error: Error) => { - let errorMessage = "Unknown error"; + let errorMessage = "Failed to sign in"; if (isAxiosError(error)) { if (error.response) { @@ -52,6 +55,31 @@ export const Login = () => { }); }; + const handleGoogleSignInSuccess = ( + response: Omit + ) => { + axios + .post("/auth/google", { + code: response.code + }) + .then(() => { + const urlParams = new URLSearchParams(window.location.search); + const returnUrl = urlParams.get("return_url"); + if (returnUrl) { + window.location.href = returnUrl; + } + }) + .catch((error: Error) => { + // eslint-disable-next-line no-console + console.error(error); + setError("Failed to sign in with Google"); + }); + }; + + const handleGoogleSignInError = () => { + setError("Failed to sign in with Google"); + }; + return ( @@ -70,12 +98,14 @@ export const Login = () => { type={"text"} value={username} onChange={handleUsernameChange} + autoComplete={"email"} /> { isDisabled={Boolean(!username || !password)} /> + {isString(window.googleClientId) && + window.googleClientId.length > 0 && ( + <> + + + or + + + + + )} By signing in you agree with theme.colors.v3.surface.secondary}; @@ -60,7 +61,7 @@ export const Subtitle = styled.span` `; export const Form = styled.form` - margin-top: 16px; + margin-top: 4px; display: flex; flex-direction: column; gap: 16px; @@ -71,6 +72,22 @@ export const SignInButton = styled(NewButton)` justify-content: center; `; +export const SignInSeparator = styled.div` + ${caption1BoldTypography} + + display: flex; + align-items: center; + text-align: center; + color: ${({ theme }) => theme.colors.v3.text.tertiary}; + text-transform: uppercase; + gap: 8px; +`; + +export const Divider = styled.div` + flex-grow: 1; + border-bottom: 1px solid ${({ theme }) => theme.colors.v3.stroke.dark}; +`; + export const Footer = styled.div` ${subscriptRegularTypography} diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts index 6dce4be31..b4b9d6ebc 100644 --- a/src/components/common/App/ConfigContext.ts +++ b/src/components/common/App/ConfigContext.ts @@ -34,7 +34,8 @@ export const initialState: ConfigContextData = { userId: isString(window.userId) ? window.userId : "", isDigmathonGameFinished: window.isDigmathonGameFinished === true, runConfig: undefined, - areInsightSuggestionsEnabled: window.areInsightSuggestionsEnabled === true + areInsightSuggestionsEnabled: window.areInsightSuggestionsEnabled === true, + googleClientId: isString(window.googleClientId) ? window.googleClientId : "" }; export const ConfigContext = createContext(initialState); diff --git a/src/components/common/App/types.ts b/src/components/common/App/types.ts index 247cf034e..fd3029091 100644 --- a/src/components/common/App/types.ts +++ b/src/components/common/App/types.ts @@ -183,6 +183,7 @@ export interface ConfigContextData { userInfo?: UserInfo; runConfig?: RunConfiguration; areInsightSuggestionsEnabled: boolean; + googleClientId: string; } export interface InsightStats { diff --git a/src/components/common/icons/20px/GoogleLogoIcon.tsx b/src/components/common/icons/20px/GoogleLogoIcon.tsx new file mode 100644 index 000000000..4f5fa025a --- /dev/null +++ b/src/components/common/icons/20px/GoogleLogoIcon.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { useIconProps } from "../hooks"; +import type { IconProps } from "../types"; + +export const GoogleLogoIconComponent = (props: IconProps) => { + const { size } = useIconProps(props); + + return ( + + + + + + + ); +}; + +export const GoogleLogoIcon = React.memo(GoogleLogoIconComponent); diff --git a/src/components/common/v3/TextField/index.tsx b/src/components/common/v3/TextField/index.tsx index 6fbd4814b..6a19392fb 100644 --- a/src/components/common/v3/TextField/index.tsx +++ b/src/components/common/v3/TextField/index.tsx @@ -15,7 +15,8 @@ const TextFieldComponent = ( disabled, inputEndContent, error, - alwaysRenderError + alwaysRenderError, + autoComplete }: TextFieldProps, ref: ForwardedRef ) => { @@ -49,6 +50,7 @@ const TextFieldComponent = ( disabled={disabled} $isInvalid={isInvalid} ref={ref} + autoComplete={autoComplete} /> {inputEndContent} diff --git a/src/components/common/v3/TextField/types.ts b/src/components/common/v3/TextField/types.ts index 6e2050f3d..aea2a2c3a 100644 --- a/src/components/common/v3/TextField/types.ts +++ b/src/components/common/v3/TextField/types.ts @@ -1,6 +1,7 @@ import type { ChangeEventHandler, ComponentType, + HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, ReactNode } from "react"; @@ -18,6 +19,7 @@ export interface TextFieldProps { type?: HTMLInputTypeAttribute; icon?: ComponentType; alwaysRenderError?: boolean; + autoComplete?: HTMLInputAutoCompleteAttribute; } export interface ContainerProps { diff --git a/src/containers/Login/GoogleAuthHoC/index.tsx b/src/containers/Login/GoogleAuthHoC/index.tsx new file mode 100644 index 000000000..e1cf86e60 --- /dev/null +++ b/src/containers/Login/GoogleAuthHoC/index.tsx @@ -0,0 +1,18 @@ +import { GoogleOAuthProvider } from "@react-oauth/google"; +import { useConfigSelector } from "../../../store/config/useConfigSelector"; +import { isString } from "../../../typeGuards/isString"; +import type { GoogleAuthHocProps } from "./types"; + +export const GoogleAuthHoC = ({ children }: GoogleAuthHocProps) => { + const { googleClientId } = useConfigSelector(); + + if (!isString(googleClientId) || !googleClientId.length) { + return children; + } + + return ( + + {children} + + ); +}; diff --git a/src/containers/Login/GoogleAuthHoC/types.ts b/src/containers/Login/GoogleAuthHoC/types.ts new file mode 100644 index 000000000..f11f3a8d0 --- /dev/null +++ b/src/containers/Login/GoogleAuthHoC/types.ts @@ -0,0 +1,5 @@ +import type { ReactNode } from "react"; + +export interface GoogleAuthHocProps { + children: ReactNode; +} diff --git a/src/containers/Login/index.tsx b/src/containers/Login/index.tsx index f6314783a..0a6fa624c 100644 --- a/src/containers/Login/index.tsx +++ b/src/containers/Login/index.tsx @@ -2,6 +2,7 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { App } from "../../components/common/App"; import { Login } from "../../components/Login"; +import { GoogleAuthHoC } from "./GoogleAuthHoC"; const APP_ID = "login"; @@ -12,7 +13,9 @@ if (rootElement) { root.render( - + + + ); diff --git a/src/globals.d.ts b/src/globals.d.ts index 065bdfa7d..0315580ea 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -53,6 +53,7 @@ declare global { isDigmathonGameFinished?: unknown; isLoggingEnabled?: unknown; areInsightSuggestionsEnabled?: unknown; + googleClientId?: unknown; } } From f39774b4dec960ba7b833ad189f987c43c84a2c4 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 20 Jan 2025 15:23:04 +0100 Subject: [PATCH 2/7] Fix types --- src/store/config/configSlice.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/store/config/configSlice.ts b/src/store/config/configSlice.ts index 28c528c8a..936659619 100644 --- a/src/store/config/configSlice.ts +++ b/src/store/config/configSlice.ts @@ -41,6 +41,7 @@ export interface ConfigState { selectedServices: string[] | null; scope: Scope | null; areInsightSuggestionsEnabled: boolean | null; + googleClientId: string | null; } const initialState: ConfigState = { @@ -94,7 +95,8 @@ const initialState: ConfigState = { scope: null, areInsightSuggestionsEnabled: isBoolean(window.areInsightSuggestionsEnabled) ? window.areInsightSuggestionsEnabled - : null + : null, + googleClientId: isString(window.googleClientId) ? window.googleClientId : null }; const set = (update: Partial) => (state: ConfigState) => ({ From f0a0f7ed2e6653e790611ddf2e5fe68650fae196 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Jan 2025 14:30:28 +0000 Subject: [PATCH 3/7] Bump version to 6.2.0-alpha.1 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b2fef226..68d88d349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "6.1.0", + "version": "6.2.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "6.1.0", + "version": "6.2.0-alpha.1", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.25.1", diff --git a/package.json b/package.json index b9b29dd9f..ee74fd05d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "6.1.0", + "version": "6.2.0-alpha.1", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From dac06dd54e5f6620da1268a4601bd36ebc3862cd Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 20 Jan 2025 20:26:26 +0100 Subject: [PATCH 4/7] Fix N+1 insight stats --- .storybook/preview.tsx | 10 ++++-- .storybook/store.ts | 13 +++++++ .../Admin/Reports/CodeIssues/index.tsx | 19 ++++++++--- .../Dashboard/MetricsReport/index.tsx | 7 ++-- .../index.tsx | 2 +- .../SpaNPlusOneInsightCard/index.tsx | 19 ++++++++--- .../AffectedEndpointsSelector/index.tsx | 5 ++- src/components/common/IssuesReport/index.tsx | 34 ++++++++++++------- src/components/common/IssuesReport/types.ts | 12 +++++-- 9 files changed, 87 insertions(+), 34 deletions(-) create mode 100644 .storybook/store.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 7625687f3..a244eef30 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,6 +2,7 @@ import type { Preview, StoryFn } from "@storybook/react"; // TODO: remove React import due to new JSX transform // eslint-disable-next-line @typescript-eslint/no-unused-vars import React, { useEffect, useState } from "react"; +import { Provider } from "react-redux"; import { withRouter } from "storybook-addon-remix-react-router"; import { cancelMessage, @@ -11,6 +12,7 @@ import { import { App, THEMES } from "../src/components/common/App"; import { dispatcher } from "../src/dispatcher"; import type { Theme } from "../src/globals"; +import { store } from "./store"; const preview: Preview = { decorators: [ @@ -32,9 +34,11 @@ const preview: Preview = { }, []); return isInitialized ? ( - - - + + + + + ) : ( <>Initializing... ); diff --git a/.storybook/store.ts b/.storybook/store.ts new file mode 100644 index 000000000..4dbc4aa07 --- /dev/null +++ b/.storybook/store.ts @@ -0,0 +1,13 @@ +import { configureStore } from "@reduxjs/toolkit"; +import { setupListeners } from "@reduxjs/toolkit/query"; +import { digmaApi } from "../src/redux/services/digma"; + +export const store = configureStore({ + reducer: { + [digmaApi.reducerPath]: digmaApi.reducer + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware().concat(digmaApi.middleware) +}); + +setupListeners(store.dispatch); diff --git a/src/components/Admin/Reports/CodeIssues/index.tsx b/src/components/Admin/Reports/CodeIssues/index.tsx index 44e984cce..0c0d0f243 100644 --- a/src/components/Admin/Reports/CodeIssues/index.tsx +++ b/src/components/Admin/Reports/CodeIssues/index.tsx @@ -22,6 +22,7 @@ import { type IssuesReportViewMode } from "../../../../redux/slices/issuesReportSlice"; import { IssuesReport } from "../../../common/IssuesReport"; +import type { TargetScope } from "../../../common/IssuesReport/types"; import { IssuesSidebar } from "./IssuesSidebar"; import * as s from "./styles"; @@ -64,14 +65,21 @@ export const CodeIssues = () => { }; }); - const handleTileIssuesStatsClick = ( - _: IssuesReportViewLevel, - target: { value: string; displayName?: string } + const handleTileTitleClick = ( + viewLevel: IssuesReportViewLevel, + target: TargetScope ) => { - if (!selectedEnvironmentId) { - return; + if (viewMode === "table" && viewLevel === "endpoints") { + setScope(target); + setIsSidebarOpen(true); + setActiveTileIds([target.value]); } + }; + const handleTileIssuesStatsClick = ( + _: IssuesReportViewLevel, + target: TargetScope + ) => { setScope(target); setIsSidebarOpen(true); setActiveTileIds([target.value]); @@ -131,6 +139,7 @@ export const CodeIssues = () => { viewMode={viewMode} timeMode={timeMode} defaultHeaderTitle={"Code Issues"} + onTileTitleClick={handleTileTitleClick} onTileIssuesStatsClick={handleTileIssuesStatsClick} onSelectedEnvironmentIdChange={handleSelectedEnvironmentIdChange} onSelectedServicesChange={handleSelectedServicesChange} diff --git a/src/components/Dashboard/MetricsReport/index.tsx b/src/components/Dashboard/MetricsReport/index.tsx index 5ed53a3d3..303f8eecf 100644 --- a/src/components/Dashboard/MetricsReport/index.tsx +++ b/src/components/Dashboard/MetricsReport/index.tsx @@ -23,6 +23,7 @@ import { isString } from "../../../typeGuards/isString"; import { SCOPE_CHANGE_EVENTS } from "../../../types"; import { changeScope } from "../../../utils/actions/changeScope"; import { IssuesReport } from "../../common/IssuesReport"; +import type { TargetScope } from "../../common/IssuesReport/types"; import { DigmaLogoIcon } from "../../common/icons/16px/DigmaLogoIcon"; import { actions } from "../actions"; import * as s from "./styles"; @@ -96,11 +97,11 @@ export const MetricsReport = () => { const handleTileTitleClick = ( viewLevel: IssuesReportViewLevel, - value: string + target: TargetScope ) => { if (viewLevel === "endpoints" && selectedEnvironmentId && selectedService) { goToEndpointIssues({ - spanCodeObjectId: value, + spanCodeObjectId: target.value, service: selectedService, environmentId: selectedEnvironmentId }); @@ -109,7 +110,7 @@ export const MetricsReport = () => { const handleIssuesStatsClick = ( viewLevel: IssuesReportViewLevel, - target: { value: string; displayName?: string } + target: TargetScope ) => { if (viewLevel === "services") { changeScope({ diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointSpanNPlusOneInsightInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointSpanNPlusOneInsightInsightCard/index.tsx index 6068aed74..f64f54a97 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointSpanNPlusOneInsightInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointSpanNPlusOneInsightInsightCard/index.tsx @@ -78,7 +78,7 @@ export const EndpointSpanNPlusOneInsightCard = ({ {span.occurrences} diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx index 37fe464e5..2e59a81fe 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/SpaNPlusOneInsightCard/index.tsx @@ -90,14 +90,26 @@ export const SpaNPlusOneInsightCard = ({ setSelectedEndpointKey(endpointKey); }; - const selectedEndpoint = useMemo( + const selectedOption = useMemo( () => selectedEndpointKey + ? selectorOptions.find((x) => getEndpointKey(x) === selectedEndpointKey) + : undefined, + [selectedEndpointKey, selectorOptions] + ); + + const selectedEndpoint = useMemo( + () => + selectedOption ? endpoints.find( - (x) => getEndpointKey(x.endpointInfo) === selectedEndpointKey + (x) => + x.endpointInfo.route === selectedOption.route && + x.endpointInfo.serviceName === selectedOption.serviceName && + x.endpointInfo.entrySpanCodeObjectId === + selectedOption.spanCodeObjectId ) : undefined, - [selectedEndpointKey, endpoints] + [selectedOption, endpoints] ); return ( @@ -135,7 +147,6 @@ export const SpaNPlusOneInsightCard = ({ )} - {selectedEndpoint && ( { - return [option.serviceName, option.spanCodeObjectId].join(DELIMITER); -}; +export const getEndpointKey = (option: Option): string => + [option.serviceName, option.spanCodeObjectId].join(DELIMITER); const renderOptions = ( endpoints: Option[], diff --git a/src/components/common/IssuesReport/index.tsx b/src/components/common/IssuesReport/index.tsx index 8b0b15347..68476e6ee 100644 --- a/src/components/common/IssuesReport/index.tsx +++ b/src/components/common/IssuesReport/index.tsx @@ -11,7 +11,8 @@ import type { EndpointIssuesData, GetMetricsReportDataPayloadV1, GetMetricsReportDataPayloadV2, - ServiceIssuesData + ServiceIssuesData, + SetEndpointsIssuesPayload } from "../../../redux/services/types"; import { FeatureFlag } from "../../../types"; import { Chart } from "./Chart"; @@ -26,6 +27,13 @@ import { transformServicesData } from "./utils"; +const getEndpointDisplayName = ( + endpointsIssues: SetEndpointsIssuesPayload | undefined, + value: string +): string | undefined => + endpointsIssues?.reports.find((x) => x.spanCodeObjectId === value) + ?.displayName; + export const IssuesReport = ({ viewMode, viewLevel, @@ -171,22 +179,22 @@ export const IssuesReport = ({ onSelectedServiceChange(value); onViewLevelChange("endpoints"); } - onTileTitleClick?.(viewLevel, value); + + const displayName = + viewLevel === "endpoints" + ? getEndpointDisplayName(endpointsIssues, value) + : undefined; + + onTileTitleClick?.(viewLevel, { value, displayName }); }; const handleIssuesStatsClick = (value: string) => { - if (viewLevel === "services") { - onTileIssuesStatsClick(viewLevel, { value }); - } + const displayName = + viewLevel === "endpoints" + ? getEndpointDisplayName(endpointsIssues, value) + : undefined; - if (viewLevel === "endpoints") { - onTileIssuesStatsClick(viewLevel, { - value, - displayName: endpointsIssues?.reports.find( - (x) => x.spanCodeObjectId === value - )?.displayName - }); - } + onTileIssuesStatsClick(viewLevel, { value, displayName }); }; const handleGoBack = () => { diff --git a/src/components/common/IssuesReport/types.ts b/src/components/common/IssuesReport/types.ts index 3cda22ce0..093e2ee79 100644 --- a/src/components/common/IssuesReport/types.ts +++ b/src/components/common/IssuesReport/types.ts @@ -6,6 +6,11 @@ import type { } from "../../../redux/slices/issuesReportSlice"; import type { Severity } from "../../common/IssuesReport/Table/types"; +export interface TargetScope { + value: string; + displayName?: string; +} + export interface IssuesReportProps { viewMode: IssuesReportViewMode; viewLevel: IssuesReportViewLevel; @@ -24,10 +29,13 @@ export interface IssuesReportProps { onPeriodInDaysChange: (periodInDays: number) => void; onTimeModeChange: (timeMode: IssuesReportTimeMode) => void; onViewModeChange: (viewMode: IssuesReportViewMode) => void; - onTileTitleClick?: (viewLevel: IssuesReportViewLevel, value: string) => void; + onTileTitleClick?: ( + viewLevel: IssuesReportViewLevel, + target: TargetScope + ) => void; onTileIssuesStatsClick: ( viewLevel: IssuesReportViewLevel, - target: { value: string; displayName?: string } + target: TargetScope ) => void; onViewLevelChange: (viewLevel: IssuesReportViewLevel) => void; onSelectedServiceChange: (service: string | null) => void; From bb5d7faabb21b52d11c144e18fe3c37eea3de6a9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 20 Jan 2025 19:35:27 +0000 Subject: [PATCH 5/7] Bump version to 6.2.0-alpha.2 [skip ci] --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 68d88d349..e57f3c006 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "digma-ui", - "version": "6.2.0-alpha.1", + "version": "6.2.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digma-ui", - "version": "6.2.0-alpha.1", + "version": "6.2.0-alpha.2", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.25.1", diff --git a/package.json b/package.json index ee74fd05d..d921b91a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "digma-ui", - "version": "6.2.0-alpha.1", + "version": "6.2.0-alpha.2", "description": "Digma UI", "scripts": { "lint:eslint": "eslint --cache .", From fdb67d6a9783d727e9b0d0598ddca475aab45eef Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 21 Jan 2025 13:47:49 +0100 Subject: [PATCH 6/7] Add repeats count to the Excessive API calls insight --- .../Admin/Reports/CodeIssues/index.tsx | 8 +++++- ...ndpointBottleneckInsightTicket.stories.tsx | 3 ++- .../CommitInfos/CommitInfos.stories.tsx | 2 +- .../InsightJiraTicket.stories.tsx | 3 ++- .../NPlusOneEndpoints.stories.tsx | 3 ++- .../QueryOptimizationEndpoints.stories.tsx | 3 ++- .../IssuesFilter/IssuesFilter.stories.tsx | 2 +- .../EndpointChattyApiV2InsightCard/index.tsx | 26 +++++++++++++------ .../EndpointChattyApiV2InsightCard/styles.ts | 5 ++++ .../DurationChange/DurationChange.stories.tsx | 2 +- .../ErrorCard/index.tsx | 3 +++ .../Digmathon/ProgressView/index.tsx | 6 ++++- .../AffectedEndpointsSelector.stories.tsx | 2 +- 13 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/components/Admin/Reports/CodeIssues/index.tsx b/src/components/Admin/Reports/CodeIssues/index.tsx index 0c0d0f243..3255c8a2d 100644 --- a/src/components/Admin/Reports/CodeIssues/index.tsx +++ b/src/components/Admin/Reports/CodeIssues/index.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import { CSSTransition } from "react-transition-group"; import { useAdminDispatch, @@ -32,6 +32,8 @@ export const CodeIssues = () => { const [activeTileIds, setActiveTileIds] = useState( undefined ); + const sidebarContainerRef = useRef(null); + const overlayRef = useRef(null); const selectedEnvironmentId = useAdminSelector( (state) => state.codeIssuesReport.selectedEnvironmentId @@ -158,8 +160,10 @@ export const CodeIssues = () => { classNames={s.overlayTransitionClassName} mountOnEnter={true} unmountOnExit={true} + nodeRef={overlayRef} > { classNames={s.sidebarContainerTransitionClassName} mountOnEnter={true} unmountOnExit={true} + nodeRef={sidebarContainerRef} > diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointBottleneckInsightTicket/EndpointBottleneckInsightTicket.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointBottleneckInsightTicket/EndpointBottleneckInsightTicket.stories.tsx index 10652869e..e34184179 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointBottleneckInsightTicket/EndpointBottleneckInsightTicket.stories.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/EndpointBottleneckInsightTicket/EndpointBottleneckInsightTicket.stories.tsx @@ -4,7 +4,8 @@ import { mockedEndpointBottleneckInsight } from "../../../InsightsCatalog/Insigh // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/insightTickets/EndpointBottleneckInsightTicket", + title: + "Insights/InsightTicketRenderer/insightTickets/EndpointBottleneckInsightTicket", component: EndpointBottleneckInsightTicket, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/CommitInfos/CommitInfos.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/CommitInfos/CommitInfos.stories.tsx index 5d97159b5..e425ae556 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/CommitInfos/CommitInfos.stories.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/CommitInfos/CommitInfos.stories.tsx @@ -4,7 +4,7 @@ import { mockedSpaNPlusOneInsight } from "../../../../InsightsCatalog/InsightsPa // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/insightTickets/common/CommitInfos", + title: "Insights/InsightTicketRenderer/insightTickets/common/CommitInfos", component: CommitInfos, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/InsightJiraTicket/InsightJiraTicket.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/InsightJiraTicket/InsightJiraTicket.stories.tsx index 603a9f244..c5593ee46 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/InsightJiraTicket/InsightJiraTicket.stories.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/InsightJiraTicket/InsightJiraTicket.stories.tsx @@ -6,7 +6,8 @@ import { InsightCategory, InsightScope } from "../../../../types"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/insightTickets/common/InsightJiraTicket", + title: + "Insights/InsightTicketRenderer/insightTickets/common/InsightJiraTicket", component: InsightJiraTicket, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/NPlusOneEndpoints/NPlusOneEndpoints.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/NPlusOneEndpoints/NPlusOneEndpoints.stories.tsx index 7d403a85d..8a081ce97 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/NPlusOneEndpoints/NPlusOneEndpoints.stories.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/NPlusOneEndpoints/NPlusOneEndpoints.stories.tsx @@ -4,7 +4,8 @@ import { mockedSpaNPlusOneInsight } from "../../../../InsightsCatalog/InsightsPa // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/insightTickets/common/NPlusOneEndpoints", + title: + "Insights/InsightTicketRenderer/insightTickets/common/NPlusOneEndpoints", component: NPlusOneEndpoints, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightTicketRenderer/insightTickets/common/QueryOptimizationEndpoints/QueryOptimizationEndpoints.stories.tsx b/src/components/Insights/InsightTicketRenderer/insightTickets/common/QueryOptimizationEndpoints/QueryOptimizationEndpoints.stories.tsx index 509662f6c..f097f112d 100644 --- a/src/components/Insights/InsightTicketRenderer/insightTickets/common/QueryOptimizationEndpoints/QueryOptimizationEndpoints.stories.tsx +++ b/src/components/Insights/InsightTicketRenderer/insightTickets/common/QueryOptimizationEndpoints/QueryOptimizationEndpoints.stories.tsx @@ -4,7 +4,8 @@ import { mockedSpanQueryOptimizationInsight } from "../../../../InsightsCatalog/ // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/insightTickets/common/QueryOptimizationEndpoints", + title: + "Insights/InsightTicketRenderer/insightTickets/common/QueryOptimizationEndpoints", component: QueryOptimizationEndpoints, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx index 060bf8211..969ea7846 100644 --- a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/IssuesFilter.stories.tsx @@ -5,7 +5,7 @@ import { actions } from "../../../Issues/actions"; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { - title: "Insights/Issues/InsightsCatalog/FilterPanel/IssuesFilter", + title: "Insights/InsightsCatalog/FilterPanel/IssuesFilter", component: IssuesFilter, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/index.tsx index 6bbe33f41..6f3005abd 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/index.tsx @@ -1,6 +1,9 @@ +import { Tag } from "../../../../../../common/v3/Tag"; import type { InsightType, Trace } from "../../../../../types"; import { InsightCard } from "../common/InsightCard"; -import { AssetLink, Description } from "../styles"; +import { ColumnsContainer } from "../common/InsightCard/ColumnsContainer"; +import { KeyValue } from "../common/InsightCard/KeyValue"; +import { AssetLink } from "../styles"; import * as s from "./styles"; import type { EndpointChattyApiV2InsightCardProps } from "./types"; @@ -30,19 +33,26 @@ export const EndpointChattyApiV2InsightCard = ({ const spanName = insight.span.clientSpan.displayName; const traceId = insight.span.traceId; const spanCodeObjectId = insight.span.clientSpan.spanCodeObjectId; + const repeats = insight.span.repeats; return ( - - Excessive API calls to specific endpoint found - - handleSpanLinkClick(spanCodeObjectId)} - /> + + + handleSpanLinkClick(spanCodeObjectId)} + /> + + + + + } onRecalculate={onRecalculate} diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/styles.ts index 1a47b2866..44894a341 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/styles.ts +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/EndpointChattyApiV2InsightCard/styles.ts @@ -1,6 +1,11 @@ import styled from "styled-components"; +import { KeyValue } from "../common/InsightCard/KeyValue"; import { ContentContainer } from "../styles"; export const Container = styled(ContentContainer)` gap: 4px; `; + +export const DescriptionColumn = styled(KeyValue)` + flex-grow: 2; +`; diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/DurationChange/DurationChange.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/DurationChange/DurationChange.stories.tsx index 89142ac96..74deede39 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/DurationChange/DurationChange.stories.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/DurationChange/DurationChange.stories.tsx @@ -4,7 +4,7 @@ import { DurationChange } from "."; // More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction const meta: Meta = { title: - "Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightsCards/common/DurationChange", + "Insights/InsightsCatalog/InsightsPage/InsightCardRenderer/insightCards/common/DurationChange", component: DurationChange, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout diff --git a/src/components/RecentActivity/CreateEnvironmentWizard/ErrorCard/index.tsx b/src/components/RecentActivity/CreateEnvironmentWizard/ErrorCard/index.tsx index dc9ef0ef3..c5d233b2b 100644 --- a/src/components/RecentActivity/CreateEnvironmentWizard/ErrorCard/index.tsx +++ b/src/components/RecentActivity/CreateEnvironmentWizard/ErrorCard/index.tsx @@ -13,6 +13,7 @@ const DEFAULT_TRANSITION_DURATION = 500; // in milliseconds export const ErrorCard = ({ title, description }: ErrorCardProps) => { const [isVisible, setIsVisible] = useState(true); const hideTimerId = useRef(); + const containerRef = useRef(null); const startTimer = () => { hideTimerId.current = window.setTimeout(() => { @@ -33,8 +34,10 @@ export const ErrorCard = ({ title, description }: ErrorCardProps) => { timeout={DEFAULT_TRANSITION_DURATION} classNames={TRANSITION_CLASS_NAME} unmountOnExit={true} + nodeRef={containerRef} > { diff --git a/src/components/RecentActivity/Digmathon/ProgressView/index.tsx b/src/components/RecentActivity/Digmathon/ProgressView/index.tsx index 1955ad774..35c49fdc5 100644 --- a/src/components/RecentActivity/Digmathon/ProgressView/index.tsx +++ b/src/components/RecentActivity/Digmathon/ProgressView/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useEffect } from "react"; +import { useContext, useEffect, useRef } from "react"; import { CSSTransition } from "react-transition-group"; import { isString } from "../../../../typeGuards/isString"; import { openURLInDefaultBrowser } from "../../../../utils/actions/openURLInDefaultBrowser"; @@ -20,7 +20,9 @@ const DIGMATHON_URL = "https://www.digma.ai/digmathon"; const DIGMATHON_LEADERBOARD_URL = "https://www.digma.ai/digmathon/#leaderboard"; export const ProgressView = ({ data }: ProgressViewProps) => { + const newIssuesFoundMessageRef = useRef(null); const config = useContext(ConfigContext); + useEffect(() => { sendTrackingEvent(trackingEvents.DIGMATHON_PROGRESS_VIEWED); }, []); @@ -103,8 +105,10 @@ export const ProgressView = ({ data }: ProgressViewProps) => { classNames={s.NEW_ISSUES_FOUND_MESSAGE_ANIMATION_CLASS_NAME} unmountOnExit={true} mountOnEnter={true} + nodeRef={newIssuesFoundMessageRef} > = { - title: "Insights/InsightsCatalog/InsightPage/AffectedEndpointsSelector", + title: "Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector", component: AffectedEndpointsSelector, parameters: { // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout From 9a754ea41e076ddcc6f6cec6a3cfb326a45de854 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 21 Jan 2025 14:25:50 +0100 Subject: [PATCH 7/7] Sort services by name in filters --- src/components/Assets/AssetsFilter/index.tsx | 15 +++++++++++---- .../GlobalErrorsFilters/index.tsx | 14 ++++++++------ .../FilterPanel/IssuesFilter/index.tsx | 14 ++++++++------ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/components/Assets/AssetsFilter/index.tsx b/src/components/Assets/AssetsFilter/index.tsx index e95c5458e..ed267b528 100644 --- a/src/components/Assets/AssetsFilter/index.tsx +++ b/src/components/Assets/AssetsFilter/index.tsx @@ -23,6 +23,7 @@ import { trackingEvents } from "../tracking"; import * as s from "./styles"; import type { AssetFilterCategory, + AssetFilterEntry, AssetsFiltersData, GetAssetFiltersDataParams, GetAssetFiltersDataPayload @@ -57,10 +58,14 @@ const renderFilterCategory = ( placeholder: string, selectedValues: string[], onChange: (value: string | string[], categoryName?: string) => void, - transformLabel?: (value: string) => string + transformLabel?: (value: string) => string, + sorter?: (a: AssetFilterEntry, b: AssetFilterEntry) => number ): JSX.Element => { + const sortedEntries = + (sorter ? category.entries?.sort(sorter) : category.entries) ?? []; + const items = - category.entries?.map((entry) => ({ + sortedEntries.map((entry) => ({ value: entry.name, label: transformLabel ? transformLabel(entry.name) : entry.name, enabled: entry.enabled, @@ -76,7 +81,7 @@ const renderFilterCategory = ( placeholder={placeholder} multiselect={true} icon={icon} - disabled={category.entries?.length === 0} + disabled={sortedEntries.length === 0} /> ); }; @@ -524,7 +529,9 @@ export const AssetsFilter = () => { WrenchIcon, selectedServices.length > 0 ? "Services" : "All", selectedServices, - handleSelectedItemsChange + handleSelectedItemsChange, + undefined, + (a, b) => a.name.localeCompare(b.name) ) }); } diff --git a/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx b/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx index d61763b4c..f249ea6b3 100644 --- a/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx +++ b/src/components/Errors/GlobalErrorsList/GlobalErrorsFilters/index.tsx @@ -210,12 +210,14 @@ export const GlobalErrorsFilters = () => { const servicesFilterOptions: SelectItem[] = useMemo( () => - services?.map((x) => ({ - label: x, - value: x, - selected: selectedServices.includes(x), - enabled: true - })) ?? [], + services + ?.map((x) => ({ + label: x, + value: x, + selected: selectedServices.includes(x), + enabled: true + })) + .sort((a, b) => a.label.localeCompare(b.label)) ?? [], [services, selectedServices] ); diff --git a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx index da9fd024f..2e8f80bbf 100644 --- a/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/IssuesFilter/index.tsx @@ -219,12 +219,14 @@ export const IssuesFilter = () => { }; const servicesFilterOptions: SelectItem[] = - data?.services?.map((entry) => ({ - value: entry, - label: entry, - enabled: true, - selected: selectedServices.includes(entry) - })) ?? []; + data?.services + ?.map((entry) => ({ + value: entry, + label: entry, + enabled: true, + selected: selectedServices.includes(entry) + })) + .sort((a, b) => a.label.localeCompare(b.label)) ?? []; const issueTypesFilterOptions: SelectItem[] = data?.issueTypeFilters?.map((entry) => ({