Skip to content

Commit df031e6

Browse files
Add Analytics tab to the Web (#1332)
* Add Analytics tab to the Web * Add No filtered issues Empty state
1 parent 252eb02 commit df031e6

File tree

29 files changed

+486
-139
lines changed

29 files changed

+486
-139
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import {
3+
useGetAboutQuery,
4+
useGetInsightsQuery
5+
} from "../../../../../../redux/services/digma";
6+
import {
7+
InsightsSortingCriterion,
8+
SortingOrder
9+
} from "../../../../../../redux/services/types";
10+
import type { ChangeScopePayload } from "../../../../../../utils/actions/changeScope";
11+
import { sendUserActionTrackingEvent } from "../../../../../../utils/actions/sendUserActionTrackingEvent";
12+
import { Pagination } from "../../../../../common/Pagination";
13+
import { NewButton } from "../../../../../common/v3/NewButton";
14+
import { EmptyState } from "../../../../../Insights/EmptyState";
15+
import { EmptyState as InsightsPageEmptyState } from "../../../../../Insights/InsightsCatalog/InsightsPage/EmptyState";
16+
import { InsightCardRenderer } from "../../../../../Insights/InsightsCatalog/InsightsPage/InsightCardRenderer";
17+
import { trackingEvents } from "../../../../tracking";
18+
import * as s from "./styles";
19+
import type { AnalyticsProps } from "./types";
20+
21+
const PAGE_SIZE = 10;
22+
23+
export const Analytics = ({
24+
query,
25+
onScopeChange,
26+
onGoToAssets
27+
}: AnalyticsProps) => {
28+
const [page, setPage] = useState(0);
29+
const insightsListRef = useRef<HTMLDivElement>(null);
30+
const { data: about } = useGetAboutQuery();
31+
const pageSize = query?.pageSize ?? PAGE_SIZE;
32+
const { data, isFetching } = useGetInsightsQuery({
33+
data: {
34+
environment: query?.environment,
35+
scopedSpanCodeObjectId: query?.scopedSpanCodeObjectId,
36+
services:
37+
query?.services && query?.services.length > 0
38+
? query?.services.join(",")
39+
: undefined,
40+
sortBy: InsightsSortingCriterion.Criticality,
41+
sortOrder: SortingOrder.Desc,
42+
page,
43+
pageSize: PAGE_SIZE
44+
},
45+
extra: {
46+
insightViewType: "Analytics"
47+
}
48+
});
49+
50+
const handleChangePage = (page: number) => {
51+
sendUserActionTrackingEvent(trackingEvents.ANALYTICS_PAGE_CHANGED);
52+
setPage(page);
53+
};
54+
55+
const handleScopeChange = (payload: ChangeScopePayload) => {
56+
onScopeChange(payload);
57+
};
58+
59+
const totalCount = data?.data.totalCount ?? 0;
60+
const pageStartItemNumber = page * pageSize + 1;
61+
const pageEndItemNumber = Math.min(
62+
pageStartItemNumber + pageSize - 1,
63+
totalCount
64+
);
65+
66+
useEffect(() => {
67+
setPage(0);
68+
}, [query]);
69+
70+
useEffect(() => {
71+
insightsListRef.current?.scrollTo(0, 0);
72+
}, [page, query]);
73+
74+
const renderEmptyState = () => {
75+
const handleSeeAllAssetsClick = () => {
76+
sendUserActionTrackingEvent(
77+
trackingEvents.ANALYTICS_SEE_ALL_ASSETS_BUTTON_CLICKED
78+
);
79+
onGoToAssets();
80+
};
81+
82+
if (!query?.scopedSpanCodeObjectId) {
83+
return (
84+
<InsightsPageEmptyState
85+
preset={"analyticsSelectAsset"}
86+
customContent={
87+
<NewButton
88+
buttonType={"primary"}
89+
onClick={handleSeeAllAssetsClick}
90+
label={"See all assets"}
91+
/>
92+
}
93+
/>
94+
);
95+
}
96+
97+
return <InsightsPageEmptyState preset={"noDataYet"} />;
98+
};
99+
100+
return (
101+
<s.Container>
102+
<s.ContentContainer>
103+
{isFetching ? (
104+
<EmptyState preset={"loading"} />
105+
) : data ? (
106+
data.data.insights.length > 0 ? (
107+
<s.InsightsList ref={insightsListRef}>
108+
{data.data.insights.map((insight) => (
109+
<InsightCardRenderer
110+
key={insight.id}
111+
insight={insight}
112+
isJiraHintEnabled={false}
113+
isMarkAsReadButtonEnabled={false}
114+
viewMode={"full"}
115+
tooltipBoundaryRef={insightsListRef}
116+
backendInfo={about ?? null}
117+
onScopeChange={handleScopeChange}
118+
/>
119+
))}
120+
</s.InsightsList>
121+
) : (
122+
renderEmptyState()
123+
)
124+
) : null}
125+
</s.ContentContainer>
126+
<s.Footer>
127+
{totalCount > 0 && (
128+
<>
129+
<Pagination
130+
itemsCount={totalCount}
131+
page={page}
132+
pageSize={pageSize}
133+
onPageChange={handleChangePage}
134+
extendedNavigation={true}
135+
/>
136+
<s.FooterItemsCount>
137+
Showing{" "}
138+
<s.FooterPageItemsCount>
139+
{pageStartItemNumber} - {pageEndItemNumber}
140+
</s.FooterPageItemsCount>{" "}
141+
of {totalCount}
142+
</s.FooterItemsCount>
143+
</>
144+
)}
145+
</s.Footer>
146+
</s.Container>
147+
);
148+
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import styled from "styled-components";
2+
import { caption1RegularTypography } from "../../../../../common/App/typographies";
3+
4+
export const TRANSITION_DURATION = 300;
5+
export const drawerTransitionClassName = "drawer";
6+
7+
export const Container = styled.div`
8+
display: flex;
9+
flex-direction: column;
10+
width: 100%;
11+
`;
12+
13+
export const ContentContainer = styled.div`
14+
position: relative;
15+
flex-grow: 1;
16+
display: flex;
17+
overflow-y: auto;
18+
`;
19+
20+
export const InsightsList = styled.div`
21+
display: flex;
22+
flex-direction: column;
23+
gap: 8px;
24+
overflow-y: auto;
25+
padding: 16px 16px 16px 0;
26+
width: 100%;
27+
`;
28+
29+
export const Footer = styled.div`
30+
${caption1RegularTypography}
31+
display: flex;
32+
align-items: center;
33+
margin-top: auto;
34+
padding: 8px;
35+
gap: 8px;
36+
`;
37+
38+
export const FooterItemsCount = styled.span`
39+
font-weight: 500;
40+
color: ${({ theme }) => {
41+
switch (theme.mode) {
42+
case "light":
43+
return "#818594";
44+
case "dark":
45+
case "dark-jetbrains":
46+
return "#b4b8bf";
47+
}
48+
}};
49+
`;
50+
51+
export const FooterPageItemsCount = styled.span`
52+
color: ${({ theme }) => {
53+
switch (theme.mode) {
54+
case "light":
55+
return "#494b57";
56+
case "dark":
57+
case "dark-jetbrains":
58+
return "#dfe1e5";
59+
}
60+
}};
61+
`;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { GetIssuesPayload } from "../../../../../../redux/services/types";
2+
import type { ChangeScopePayload } from "../../../../../../utils/actions/changeScope";
3+
4+
export interface AnalyticsProps {
5+
query?: GetIssuesPayload;
6+
onScopeChange: (payload: ChangeScopePayload) => void;
7+
onGoToAssets: () => void;
8+
}

src/components/Admin/common/MainSidebarOverlay/MainSidebar/Assets/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ import type { Sorting } from "../../../../../common/SortingSelector/types";
1414
import * as s from "./styles";
1515
import type { AssetsProps } from "./types";
1616

17-
export const Assets = ({ query, onScopeChange }: AssetsProps) => {
17+
export const Assets = ({
18+
query,
19+
onScopeChange,
20+
selectedAssetTypeId,
21+
onSelectedAssetTypeIdChange
22+
}: AssetsProps) => {
1823
const [sorting, setSorting] = useState<Sorting<AssetsSortingCriterion>>({
1924
criterion: AssetsSortingCriterion.CriticalInsights,
2025
order: SortingOrder.Desc
2126
});
22-
const [selectedAssetTypeId, setSelectedAssetTypeId] = useState<AssetType>();
2327

2428
const dispatch = useAdminDispatch();
2529

@@ -37,11 +41,11 @@ export const Assets = ({ query, onScopeChange }: AssetsProps) => {
3741
);
3842

3943
const handleAssetTypeSelect = (assetTypeId: AssetType) => {
40-
setSelectedAssetTypeId(assetTypeId);
44+
onSelectedAssetTypeIdChange(assetTypeId);
4145
};
4246

4347
const handleGoToAllAssets = () => {
44-
setSelectedAssetTypeId(undefined);
48+
onSelectedAssetTypeIdChange(undefined);
4549
};
4650

4751
const handleRefresh = () => {
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import type { GetIssuesPayload } from "../../../../../../redux/services/types";
1+
import type {
2+
AssetType,
3+
GetIssuesPayload
4+
} from "../../../../../../redux/services/types";
25
import type { ChangeScopePayload } from "../../../../../../utils/actions/changeScope";
36

47
export interface AssetsProps {
58
query?: GetIssuesPayload;
69
onScopeChange: (payload: ChangeScopePayload) => void;
10+
selectedAssetTypeId?: AssetType;
11+
onSelectedAssetTypeIdChange: (assetTypeId?: AssetType) => void;
712
}

src/components/Admin/common/MainSidebarOverlay/MainSidebar/Header/index.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import useDimensions from "react-cool-dimensions";
33
import { getFeatureFlagValue } from "../../../../../../featureFlags";
44
import { usePrevious } from "../../../../../../hooks/usePrevious";
55
import { useGetAboutQuery } from "../../../../../../redux/services/digma";
6-
import { FeatureFlag } from "../../../../../../types";
6+
import { FeatureFlag, ScopeChangeEvent } from "../../../../../../types";
7+
import { sendUserActionTrackingEvent } from "../../../../../../utils/actions/sendUserActionTrackingEvent";
78
import { HistoryNavigationPanel } from "../../../../../common/HistoryNavigationPanel";
89
import { CrossIcon } from "../../../../../common/icons/16px/CrossIcon";
910
import { NewIconButton } from "../../../../../common/v3/NewIconButton";
1011
import { isDisplayNameTooLong } from "../../../../../Navigation/isDisplayNameTooLong";
1112
import { ScopeBar } from "../../../../../Navigation/ScopeBar";
1213
import { SpanInfo } from "../../../../../Navigation/SpanInfo";
1314
import { Tabs } from "../../../../../Navigation/Tabs";
15+
import { trackingEvents } from "../../../../tracking";
1416
import * as s from "./styles";
1517
import type { HeaderProps } from "./types";
1618

@@ -19,12 +21,12 @@ export const Header = ({
1921
scope,
2022
onGoBack,
2123
onGoForward,
22-
onGoHome,
2324
spanInfo,
2425
canGoBack,
2526
canGoForward,
2627
onTabSelect,
2728
selectedTabId,
29+
onScopeChange,
2830
query
2931
}: HeaderProps) => {
3032
const [isSpanInfoVisible, setIsSpanInfoVisible] = useState(false);
@@ -43,15 +45,29 @@ export const Header = ({
4345
};
4446

4547
const handleGoBack = () => {
48+
sendUserActionTrackingEvent(
49+
trackingEvents.MAIN_SIDEBAR_BACK_BUTTON_CLICKED
50+
);
4651
onGoBack();
4752
};
4853

4954
const handleGoForward = () => {
55+
sendUserActionTrackingEvent(
56+
trackingEvents.MAIN_SIDEBAR_FORWARD_BUTTON_CLICKED
57+
);
5058
onGoForward();
5159
};
5260

5361
const handleGoHome = () => {
54-
onGoHome();
62+
sendUserActionTrackingEvent(
63+
trackingEvents.MAIN_SIDEBAR_HOME_BUTTON_CLICKED
64+
);
65+
onScopeChange({
66+
span: null,
67+
context: {
68+
event: ScopeChangeEvent.NavigationHomeButtonClicked
69+
}
70+
});
5571
};
5672

5773
const handleScopeDisplayNameExpandCollapseChange = (isExpanded: boolean) => {

src/components/Admin/common/MainSidebarOverlay/MainSidebar/Header/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ import type {
22
GetIssuesPayload,
33
GetSpanInfoResponse
44
} from "../../../../../../redux/services/types";
5+
import type { ChangeScopePayload } from "../../../../../../utils/actions/changeScope";
56
import type { Scope } from "../../../../../common/App/types";
67

78
export interface HeaderProps {
89
onCloseButtonClick: () => void;
910
scope: Scope;
1011
onGoBack: () => void;
1112
onGoForward: () => void;
12-
onGoHome: () => void;
1313
canGoBack: boolean;
1414
canGoForward: boolean;
1515
spanInfo?: GetSpanInfoResponse;
1616
onTabSelect: (tabId: string) => void;
1717
selectedTabId: string;
1818
query?: GetIssuesPayload;
19+
onScopeChange: (payload: ChangeScopePayload) => void;
1920
}

src/components/Admin/common/MainSidebarOverlay/MainSidebar/Issues/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ export const Issues = ({
110110
};
111111

112112
const handleChangePage = (page: number) => {
113-
sendUserActionTrackingEvent(trackingEvents.ISSUES_SIDEBAR_PAGE_CHANGED);
113+
sendUserActionTrackingEvent(trackingEvents.ISSUES_PAGE_CHANGED);
114114
setPage(page);
115115
};
116116

117117
const handleDismissalViewModeButtonClick = () => {
118118
sendUserActionTrackingEvent(
119119
viewMode === ViewMode.All
120-
? trackingEvents.ISSUES_SIDEBAR_SHOW_ALL_BUTTON_CLICKED
121-
: trackingEvents.ISSUES_SIDEBAR_SHOW_ONLY_DISMISSED_BUTTON_CLICKED
120+
? trackingEvents.ISSUES_SHOW_ALL_BUTTON_CLICKED
121+
: trackingEvents.ISSUES_SHOW_ONLY_DISMISSED_BUTTON_CLICKED
122122
);
123123
const newMode =
124124
viewMode === ViewMode.All ? ViewMode.OnlyDismissed : ViewMode.All;

0 commit comments

Comments
 (0)