Skip to content

Commit 19d9c26

Browse files
Add rejected traces section to Web Admin (#1374)
* Add rejected traces section to Web Admin
1 parent a0f1b18 commit 19d9c26

File tree

15 files changed

+618
-68
lines changed

15 files changed

+618
-68
lines changed

src/components/Admin/Environments/index.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
useAdminSelector
1212
} from "../../../containers/Admin/hooks";
1313
import { getFeatureFlagValue } from "../../../featureFlags";
14-
import { usePagination } from "../../../hooks/usePagination";
14+
import { usePageParam } from "../../../hooks/usePageParam";
1515
import {
1616
useDeleteEnvironmentMutation,
1717
useGetAboutQuery,
@@ -40,6 +40,7 @@ export const Environments = () => {
4040
const isCreateEnvironmentSidebarOpen = useAdminSelector(
4141
(state) => state.environmentsSlice.isSidebarOpen
4242
);
43+
4344
const dispatch = useAdminDispatch();
4445
const isEnvironmentLastActiveTimestampEnabled = getFeatureFlagValue(
4546
about,
@@ -50,14 +51,23 @@ export const Environments = () => {
5051
(state) => state.environmentsSlice.environmentToDelete
5152
);
5253
const { data: environments } = useGetEnvironmentsQuery();
54+
55+
const { page, setPage } = usePageParam({
56+
data: environments,
57+
pageSize: PAGE_SIZE,
58+
total: environments?.length ?? 0
59+
});
60+
5361
const sortedEnvironments = useMemo(
5462
() => sortEnvironments(environments ?? []),
5563
[environments]
5664
);
57-
const [pageItems, page, setPage] = usePagination(
58-
sortedEnvironments,
59-
PAGE_SIZE
60-
);
65+
66+
const pageItems = useMemo(() => {
67+
const startIndex = page * PAGE_SIZE;
68+
69+
return sortedEnvironments.slice(startIndex, startIndex + PAGE_SIZE);
70+
}, [sortedEnvironments, page]);
6171

6272
const columns = [
6373
columnHelper.accessor("name", {
@@ -217,6 +227,7 @@ export const Environments = () => {
217227
onPageChange={handlePageChange}
218228
pageSize={PAGE_SIZE}
219229
withDescription={true}
230+
extendedNavigation={true}
220231
/>
221232
</>
222233
)}

src/components/Admin/Header/HeaderContent/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ export const HeaderContent = ({
88
return (
99
<s.Container>
1010
{children}
11-
<s.FilterContainer>{toolbarContent}</s.FilterContainer>
11+
{toolbarContent && (
12+
<s.FilterContainer>{toolbarContent}</s.FilterContainer>
13+
)}
1214
</s.Container>
1315
);
1416
};

src/components/Admin/Header/index.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ export const Header = () => (
3434
</HeaderContent>
3535
}
3636
/>
37+
<Route path={"troubleshooting"}>
38+
<Route
39+
path={"rejected-traces"}
40+
element={
41+
<HeaderContent>
42+
<span>Rejected Traces</span>
43+
</HeaderContent>
44+
}
45+
/>
46+
<Route
47+
path={"*"}
48+
element={
49+
<HeaderContent>
50+
<span>Troubleshooting</span>
51+
</HeaderContent>
52+
}
53+
/>
54+
</Route>
3755
</Routes>
3856
</s.Header>
3957
);

src/components/Admin/NavSidebar/NavMenu/index.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { useMemo } from "react";
2+
import { getFeatureFlagValue } from "../../../../featureFlags";
3+
import { useGetAboutQuery } from "../../../../redux/services/digma";
24
import { useConfigSelector } from "../../../../store/config/useConfigSelector";
5+
import { FeatureFlag } from "../../../../types";
36
import { GlobeIcon } from "../../../common/icons/16px/GlobeIcon";
47
import { HomeIcon } from "../../../common/icons/16px/HomeIcon";
58
import { MeterHighIcon } from "../../../common/icons/16px/MeterHighIcon";
@@ -9,6 +12,12 @@ import * as s from "./styles";
912

1013
export const NavMenu = () => {
1114
const { isSandboxModeEnabled } = useConfigSelector();
15+
const { data: about } = useGetAboutQuery();
16+
17+
const isTroubleshootingEnabled = getFeatureFlagValue(
18+
about,
19+
FeatureFlag.AreBlockedTracesEnabled
20+
);
1221

1322
const navigationItems: NavigationItem[] = useMemo(
1423
() => [
@@ -40,9 +49,25 @@ export const NavMenu = () => {
4049
route: "/reports/code-issues"
4150
}
4251
]
43-
}
52+
},
53+
...(isTroubleshootingEnabled
54+
? [
55+
{
56+
id: "troubleshooting",
57+
name: "Troubleshooting",
58+
route: "/troubleshooting",
59+
items: [
60+
{
61+
id: "rejectedTraces",
62+
name: "Rejected traces",
63+
route: "/troubleshooting/rejected-traces"
64+
}
65+
]
66+
}
67+
]
68+
: [])
4469
],
45-
[isSandboxModeEnabled]
70+
[isSandboxModeEnabled, isTroubleshootingEnabled]
4671
);
4772

4873
return (
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import {
2+
createColumnHelper,
3+
getCoreRowModel,
4+
useReactTable
5+
} from "@tanstack/react-table";
6+
import { useEffect, useRef, useState } from "react";
7+
import { useNow } from "../../../../hooks/useNow";
8+
import { usePageParam } from "../../../../hooks/usePageParam";
9+
import { useGetBlockedTracesQuery } from "../../../../redux/services/digma";
10+
import {
11+
type BlockedTrace,
12+
type GetBlockedTracesResponse
13+
} from "../../../../redux/services/types";
14+
import { isString } from "../../../../typeGuards/isString";
15+
import { openURLInDefaultBrowser } from "../../../../utils/actions/openURLInDefaultBrowser";
16+
import { formatTimeDistance } from "../../../../utils/formatTimeDistance";
17+
import { getDurationString } from "../../../../utils/getDurationString";
18+
import { TraceIcon } from "../../../common/icons/16px/TraceIcon";
19+
import { NewButton } from "../../../common/v3/NewButton";
20+
import { Pagination } from "../../../common/v3/Pagination";
21+
import { Tag } from "../../../common/v3/Tag";
22+
import { Tooltip } from "../../../common/v3/Tooltip";
23+
import { Table } from "../../common/Table";
24+
import * as s from "./styles";
25+
26+
const PAGE_SIZE = 10;
27+
28+
const columnHelper = createColumnHelper<BlockedTrace>();
29+
30+
export const RejectedTraces = () => {
31+
const containerRef = useRef<HTMLDivElement>(null);
32+
const now = useNow();
33+
const [blockedTraces, setBlockedTraces] =
34+
useState<GetBlockedTracesResponse>();
35+
36+
const { page, setPage } = usePageParam({
37+
data: blockedTraces,
38+
pageSize: PAGE_SIZE,
39+
total: blockedTraces?.total ?? 0
40+
});
41+
42+
const { data } = useGetBlockedTracesQuery({
43+
page,
44+
pageSize: PAGE_SIZE
45+
});
46+
47+
const columns = [
48+
columnHelper.accessor("asset", {
49+
header: "Asset",
50+
meta: {
51+
width: "50%",
52+
minWidth: 60
53+
},
54+
cell: (info) => {
55+
const asset = info.getValue();
56+
const assetName = `${asset.service}:${asset.span}`;
57+
return (
58+
<s.AssetNameContainer>
59+
<Tooltip title={assetName}>
60+
<s.TruncatedText>{assetName}</s.TruncatedText>
61+
</Tooltip>
62+
<s.StyledCopyButton text={assetName} />
63+
</s.AssetNameContainer>
64+
);
65+
}
66+
}),
67+
columnHelper.accessor("spans", {
68+
header: "#Spans",
69+
meta: {
70+
width: "10%",
71+
minWidth: 100,
72+
textAlign: "center"
73+
},
74+
cell: (info) => {
75+
const value = info.getValue();
76+
return (
77+
<s.SpanCounterContainer>
78+
<s.SpanCounter>
79+
<s.TruncatedText>{value ?? ""}</s.TruncatedText>
80+
</s.SpanCounter>
81+
</s.SpanCounterContainer>
82+
);
83+
}
84+
}),
85+
columnHelper.accessor("lastSpanTimestamp", {
86+
header: "Executed",
87+
meta: {
88+
width: "10%",
89+
minWidth: 60
90+
},
91+
cell: (info) => {
92+
const value = info.getValue();
93+
const title = new Date(value).toString();
94+
const timeDistanceString = formatTimeDistance(value, now, {
95+
format: "medium",
96+
withDescriptiveWords: false
97+
});
98+
99+
return <Tag title={title} content={`${timeDistanceString} ago`} />;
100+
}
101+
}),
102+
columnHelper.accessor("duration", {
103+
header: "Duration",
104+
meta: {
105+
width: "10%",
106+
minWidth: 100
107+
},
108+
cell: (info) => {
109+
const value = info.getValue();
110+
111+
return value ? <Tag content={getDurationString(value)} /> : null;
112+
}
113+
}),
114+
columnHelper.accessor("reason", {
115+
header: "Reason",
116+
meta: {
117+
width: "15%",
118+
minWidth: 60
119+
},
120+
cell: (info) => {
121+
const value = info.getValue();
122+
const reasonString = value
123+
.split(/(?=[A-Z])/)
124+
.join(" ")
125+
.toLocaleLowerCase();
126+
const formattedReasonString = `${reasonString[0].toLocaleUpperCase()}${reasonString.slice(
127+
1
128+
)}`;
129+
130+
return <s.TruncatedText>{formattedReasonString}</s.TruncatedText>;
131+
}
132+
}),
133+
columnHelper.accessor((x) => x, {
134+
header: "Actions",
135+
meta: {
136+
minWidth: 100
137+
},
138+
cell: (info) => {
139+
const value = info.getValue();
140+
141+
const handleTraceButtonClick = () => {
142+
if (isString(window.jaegerURL) && window.jaegerURL.length > 0) {
143+
let url = `${window.jaegerURL}/trace/${value.traceId}`;
144+
145+
if (value.asset.span) {
146+
url = url.concat(`?uiFind=${value.asset.span}`);
147+
}
148+
openURLInDefaultBrowser(url);
149+
}
150+
};
151+
152+
return (
153+
<NewButton
154+
icon={TraceIcon}
155+
onClick={handleTraceButtonClick}
156+
label={"Trace"}
157+
/>
158+
);
159+
}
160+
})
161+
];
162+
163+
const pageItems = data?.traces ?? [];
164+
165+
const table = useReactTable({
166+
data: pageItems,
167+
columns,
168+
getCoreRowModel: getCoreRowModel()
169+
});
170+
171+
const handlePageChange = (newPage: number) => {
172+
setPage(newPage);
173+
};
174+
175+
useEffect(() => {
176+
containerRef.current?.scrollTo(0, 0);
177+
}, [page]);
178+
179+
useEffect(() => {
180+
if (data) {
181+
setBlockedTraces(data);
182+
}
183+
}, [data]);
184+
185+
return (
186+
<s.Container ref={containerRef}>
187+
{data && (
188+
<>
189+
<Table<BlockedTrace> table={table} />
190+
<Pagination
191+
itemsCount={data.total}
192+
page={page}
193+
onPageChange={handlePageChange}
194+
pageSize={PAGE_SIZE}
195+
withDescription={true}
196+
extendedNavigation={true}
197+
/>
198+
</>
199+
)}
200+
</s.Container>
201+
);
202+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import styled from "styled-components";
2+
import { subscriptRegularTypography } from "../../../common/App/typographies";
3+
import { CopyButton } from "../../../common/v3/CopyButton";
4+
5+
export const Container = styled.div`
6+
display: flex;
7+
flex-direction: column;
8+
padding: 24px;
9+
gap: 36px;
10+
`;
11+
12+
export const TruncatedText = styled.span`
13+
text-overflow: ellipsis;
14+
white-space: nowrap;
15+
overflow: hidden;
16+
`;
17+
18+
export const StyledCopyButton = styled(CopyButton)`
19+
padding: 0 6px;
20+
display: none;
21+
`;
22+
23+
export const AssetNameContainer = styled.span`
24+
display: flex;
25+
align-items: center;
26+
overflow: hidden;
27+
28+
&:hover {
29+
${StyledCopyButton} {
30+
display: flex;
31+
}
32+
}
33+
`;
34+
35+
export const SpanCounterContainer = styled.span`
36+
display: flex;
37+
flex-grow: 1;
38+
justify-content: center;
39+
`;
40+
41+
export const SpanCounter = styled.div`
42+
border-radius: 4px;
43+
padding: 4px;
44+
border: 1px solid ${({ theme }) => theme.colors.v3.surface.primaryLight};
45+
min-width: 24px;
46+
display: flex;
47+
align-items: center;
48+
justify-content: center;
49+
${subscriptRegularTypography}
50+
`;

0 commit comments

Comments
 (0)