Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions src/components/Admin/Reports/CodeIssues/IssuesSidebar/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { useEffect, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group";
import { useTheme } from "styled-components";
import {
useAdminDispatch,
useAdminSelector
} from "../../../../../containers/Admin/hooks";
import { useGetIssuesQuery } from "../../../../../redux/services/digma";
import { setIsInsightJiraTicketHintShown } from "../../../../../redux/slices/persistSlice";
import { isUndefined } from "../../../../../typeGuards/isUndefined";
import type { Scope } from "../../../../common/App/types";
import { CrossIcon } from "../../../../common/icons/16px/CrossIcon";
Expand All @@ -15,30 +20,58 @@ import { EmptyState as InsightsPageEmptyState } from "../../../../Insights/Insig
import { InsightCardRenderer } from "../../../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer";
import { ViewMode } from "../../../../Insights/InsightsCatalog/types";
import { InsightTicketRenderer } from "../../../../Insights/InsightTicketRenderer";
import type {
GenericCodeObjectInsight,
InsightTicketInfo
import {
InsightType,
type CodeObjectInsight,
type GenericCodeObjectInsight,
type InsightTicketInfo
} from "../../../../Insights/types";
import { ScopeBar } from "../../../../Navigation/ScopeBar";
import * as s from "./styles";
import { SuggestionBar } from "./SuggestionBar";
import type { IssuesHeaderProps } from "./types";
import type { IssuesSidebarProps } from "./types";

const PAGE_SIZE = 10;

const getInsightToShowJiraHint = (insights: CodeObjectInsight[]): number => {
const insightsWithJiraButton = [
InsightType.EndpointSpanNPlusOne,
InsightType.SpaNPlusOne,
InsightType.SpanEndpointBottleneck,
InsightType.EndpointBottleneck,
InsightType.SpanQueryOptimization,
InsightType.EndpointHighNumberOfQueries,
InsightType.EndpointQueryOptimizationV2,
InsightType.SpanScaling
];

return insights.findIndex((insight) =>
insightsWithJiraButton.includes(insight.type)
);
};

export const IssuesSidebar = ({
onClose,
scope,
environmentId,
viewLevel
}: IssuesHeaderProps) => {
viewLevel,
isTransitioning,
isResizing
}: IssuesSidebarProps) => {
const [infoToOpenJiraTicket, setInfoToOpenJiraTicket] =
useState<InsightTicketInfo<GenericCodeObjectInsight>>();
const [viewMode, setViewMode] = useState<ViewMode>(ViewMode.All);
const [page, setPage] = useState(0);
const [insightIdToOpenSuggestion, setInsightIdToOpenSuggestion] =
useState<string>();
const [isDrawerTransitioning, setIsDrawerTransitioning] = useState(false);
const drawerRef = useRef<HTMLDivElement>(null);
const dispatch = useAdminDispatch();
const isInsightJiraTicketHintShown = useAdminSelector(
(state) => state.persist.isInsightJiraTicketHintShown
);
const isDrawerOpen = Boolean(insightIdToOpenSuggestion);
const issuesListRef = useRef<HTMLDivElement>(null);

const theme = useTheme();
const { data, isFetching, refetch } = useGetIssuesQuery(
Expand Down Expand Up @@ -107,6 +140,7 @@ export const IssuesSidebar = ({
insight: GenericCodeObjectInsight,
spanCodeObjectId?: string
) => {
dispatch(setIsInsightJiraTicketHintShown(true));
setInfoToOpenJiraTicket({ insight, spanCodeObjectId });
};

Expand All @@ -122,6 +156,14 @@ export const IssuesSidebar = ({
setInsightIdToOpenSuggestion(undefined);
};

const handleDrawerTransitionStart = () => {
setIsDrawerTransitioning(true);
};

const handleDrawerTransitionEnd = () => {
setIsDrawerTransitioning(false);
};

const dismissedCount = data?.dismissedCount;
const totalCount = data?.totalCount ?? 0;
const pageStartItemNumber = page * PAGE_SIZE + 1;
Expand Down Expand Up @@ -151,7 +193,7 @@ export const IssuesSidebar = ({
};

return (
<s.Container>
<s.Container $isResizing={isResizing}>
<s.Header>
<s.HeaderTitleRow>
<span>Issues</span>
Expand All @@ -171,20 +213,27 @@ export const IssuesSidebar = ({
</s.Header>
{data ? (
data.insights.length > 0 ? (
<s.IssuesList>
<s.IssuesList ref={issuesListRef}>
{environmentId &&
data.insights.map((insight) => (
data.insights.map((insight, i) => (
<InsightCardRenderer
key={insight.id}
insight={insight}
onJiraTicketCreate={handleJiraTicketPopupOpen}
isJiraHintEnabled={false}
isJiraHintEnabled={
!isInsightJiraTicketHintShown &&
!isDrawerOpen &&
!isDrawerTransitioning &&
!isTransitioning &&
i === getInsightToShowJiraHint(data.insights)
}
onRefresh={refresh}
isMarkAsReadButtonEnabled={false}
viewMode={"full"}
environmentId={environmentId}
onDismissalChange={handleDismissalChange}
onOpenSuggestion={handleOpenSuggestion}
tooltipBoundaryRef={issuesListRef}
/>
))}
</s.IssuesList>
Expand Down Expand Up @@ -246,12 +295,16 @@ export const IssuesSidebar = ({
</s.Overlay>
)}
<CSSTransition
in={Boolean(insightIdToOpenSuggestion)}
in={isDrawerOpen}
timeout={s.TRANSITION_DURATION}
classNames={s.drawerTransitionClassName}
mountOnEnter={true}
unmountOnExit={true}
nodeRef={drawerRef}
onEnter={handleDrawerTransitionStart}
onEntered={handleDrawerTransitionEnd}
onExit={handleDrawerTransitionStart}
onExited={handleDrawerTransitionEnd}
>
<s.Overlay>
<s.DrawerContainer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import {
bodyBoldTypography,
caption1RegularTypography
} from "../../../../common/App/typographies";
import type { DrawerContainerProps } from "./types";
import type { ContainerProps, DrawerContainerProps } from "./types";

export const TRANSITION_DURATION = 300;
export const drawerTransitionClassName = "drawer";

export const Container = styled.div`
export const Container = styled.div<ContainerProps>`
background: ${({ theme }) =>
`linear-gradient(0deg, ${theme.colors.v3.surface.primary} 0%, ${theme.colors.v3.surface.primary} 100%), #fff`};
display: flex;
flex-direction: column;
border-radius: 8px 0 0 8px;
height: 100%;
user-select: ${({ $isResizing }) => ($isResizing ? "none" : "auto")};
`;

export const Header = styled.div`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import type { IssuesReportViewLevel } from "../../../../../redux/slices/issuesReportSlice";

export interface IssuesHeaderProps {
export interface IssuesSidebarProps {
onClose: () => void;
environmentId?: string;
scope?: {
value: string;
displayName?: string;
};
viewLevel: IssuesReportViewLevel;
isTransitioning: boolean;
isResizing?: boolean;
}

export interface DrawerContainerProps {
$transitionDuration: number;
$transitionClassName: string;
}

export interface ContainerProps {
$isResizing?: boolean;
}
93 changes: 85 additions & 8 deletions src/components/Admin/Reports/CodeIssues/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { CSSTransition } from "react-transition-group";
import {
useAdminDispatch,
Expand All @@ -19,19 +19,44 @@ import {
type IssuesReportViewLevel,
type IssuesReportViewMode
} from "../../../../redux/slices/issuesReportSlice";
import { TwoVerticalLinesIcon } from "../../../common/icons/16px/TwoVerticalLinesIcon";
import { IssuesReport } from "../../../common/IssuesReport";
import type { TargetScope } from "../../../common/IssuesReport/types";
import { IssuesSidebar } from "./IssuesSidebar";
import * as s from "./styles";

export const MIN_SIDEBAR_WIDTH = 382; // in pixels
export const MAX_SIDEBAR_WIDTH = 640; // in pixels
export const DEFAULT_SIDEBAR_WIDTH_RATIO = 0.33;

export const getDefaultSidebarWidth = (windowWidth: number) => {
const defaultWidth = windowWidth * DEFAULT_SIDEBAR_WIDTH_RATIO;
if (defaultWidth > MAX_SIDEBAR_WIDTH) {
return MAX_SIDEBAR_WIDTH;
}

if (defaultWidth < MIN_SIDEBAR_WIDTH) {
return MIN_SIDEBAR_WIDTH;
}

return defaultWidth;
};

export const CodeIssues = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [isIssuesSidebarOpen, setIsIssuesSidebarOpen] = useState(false);
const [scope, setScope] = useState<{ value: string; displayName?: string }>();
const [activeTileIds, setActiveTileIds] = useState<string[] | undefined>(
undefined
);
const sidebarContainerRef = useRef<HTMLDivElement>(null);
const overlayRef = useRef<HTMLDivElement>(null);
const [isIssuesSidebarTransitioning, setIsIssuesSidebarTransitioning] =
useState(false);
const defaultSidebarWidth = getDefaultSidebarWidth(window.innerWidth);
const [isResizeHandlePressed, setIsResizeHandlePressed] = useState(false);
const [startX, setStartX] = useState(0);
const [left, setLeft] = useState(window.innerWidth - defaultSidebarWidth);
const [startLeft, setStartLeft] = useState(0);

const selectedEnvironmentId = useAdminSelector(
(state) => state.codeIssuesReport.selectedEnvironmentId
Expand Down Expand Up @@ -65,7 +90,7 @@ export const CodeIssues = () => {
) => {
if (viewMode === "table" && viewLevel === "endpoints") {
setScope(target);
setIsSidebarOpen(true);
setIsIssuesSidebarOpen(true);
setActiveTileIds([target.value]);
}
};
Expand All @@ -75,7 +100,7 @@ export const CodeIssues = () => {
target: TargetScope
) => {
setScope(target);
setIsSidebarOpen(true);
setIsIssuesSidebarOpen(true);
setActiveTileIds([target.value]);
};

Expand Down Expand Up @@ -116,10 +141,52 @@ export const CodeIssues = () => {
};

const handleIssuesSidebarClose = () => {
setIsSidebarOpen(false);
setIsIssuesSidebarOpen(false);
setActiveTileIds(undefined);
};

const handleIssuesSidebarTransitionStart = () => {
setIsIssuesSidebarTransitioning(true);
};

const handleIssuesSidebarTransitionEnd = () => {
setIsIssuesSidebarTransitioning(false);
};

const handleResizeHandleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
setIsResizeHandlePressed(true);
setStartX(e.clientX);
setStartLeft(left);
};

useEffect(() => {
if (!isResizeHandlePressed) {
return;
}

const handleMouseMove = (e: MouseEvent) => {
const newLeft = startLeft + (e.clientX - startX);
if (
newLeft >= window.innerWidth - MAX_SIDEBAR_WIDTH &&
newLeft <= window.innerWidth - MIN_SIDEBAR_WIDTH
) {
setLeft(newLeft);
}
};

const handleMouseUp = () => {
setIsResizeHandlePressed(false);
};

document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);

return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [isResizeHandlePressed, startX, startLeft, left]);

return (
<s.Container>
<IssuesReport
Expand Down Expand Up @@ -147,7 +214,7 @@ export const CodeIssues = () => {
activeTileIds={activeTileIds}
/>
<CSSTransition
in={isSidebarOpen}
in={isIssuesSidebarOpen}
timeout={s.TRANSITION_DURATION}
classNames={s.overlayTransitionClassName}
mountOnEnter={true}
Expand All @@ -156,30 +223,40 @@ export const CodeIssues = () => {
>
<s.Overlay
ref={overlayRef}
$isVisible={isSidebarOpen}
$isVisible={isIssuesSidebarOpen}
$transitionClassName={s.overlayTransitionClassName}
$transitionDuration={s.TRANSITION_DURATION}
onClick={handleIssuesSidebarClose}
/>
</CSSTransition>
<CSSTransition
in={isSidebarOpen}
in={isIssuesSidebarOpen}
timeout={s.TRANSITION_DURATION}
classNames={s.sidebarContainerTransitionClassName}
mountOnEnter={true}
unmountOnExit={true}
nodeRef={sidebarContainerRef}
onEnter={handleIssuesSidebarTransitionStart}
onEntered={handleIssuesSidebarTransitionEnd}
onExit={handleIssuesSidebarTransitionStart}
onExited={handleIssuesSidebarTransitionEnd}
>
<s.IssuesSidebarContainer
style={{ left }}
ref={sidebarContainerRef}
$transitionClassName={s.sidebarContainerTransitionClassName}
$transitionDuration={s.TRANSITION_DURATION}
>
<s.ResizeHandle onMouseDown={handleResizeHandleMouseDown}>
<TwoVerticalLinesIcon size={16} color={"currentColor"} />
</s.ResizeHandle>
<IssuesSidebar
isResizing={isResizeHandlePressed}
onClose={handleIssuesSidebarClose}
scope={scope}
environmentId={selectedEnvironmentId ?? undefined}
viewLevel={viewLevel}
isTransitioning={isIssuesSidebarTransitioning}
/>
</s.IssuesSidebarContainer>
</CSSTransition>
Expand Down
Loading
Loading