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
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Meta, StoryObj } from "@storybook/react";

import { GlobalErrorsList } from ".";
import { ViewMode } from "../../../store/errors/errorsSlice";
import { useStore } from "../../../store/useStore";
import { actions } from "../actions";
import { DefaultErrorList, DismissedErrorList } from "./mockData";

// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
const meta: Meta<typeof GlobalErrorsList> = {
Expand All @@ -17,4 +21,32 @@ export default meta;
type Story = StoryObj<typeof meta>;

// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const Default: Story = {};
export const Default: Story = {
play: () => {
const { setEnvironment } = useStore.getState();
setEnvironment({ id: "test-env-id", name: "test", type: "Public" });
window.setTimeout(() => {
window.postMessage({
type: "digma",
action: actions.SET_GLOBAL_ERRORS_DATA,
payload: DefaultErrorList
});
}, 100);
}
};

export const Dismissed: Story = {
play: () => {
const { setEnvironment, setGlobalErrorsViewMode } = useStore.getState();
setEnvironment({ id: "test-env-id", name: "test", type: "Public" });
setGlobalErrorsViewMode(ViewMode.OnlyDismissed);

window.setTimeout(() => {
window.postMessage({
type: "digma",
action: actions.SET_GLOBAL_ERRORS_DATA,
payload: DismissedErrorList
});
}, 100);
}
};
115 changes: 103 additions & 12 deletions src/components/Errors/GlobalErrorsList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { dispatcher } from "../../../dispatcher";
import { getFeatureFlagValue } from "../../../featureFlags";
import {
DataFetcherConfiguration,
Expand All @@ -10,14 +11,20 @@ import { usePrevious } from "../../../hooks/usePrevious";
import { useConfigSelector } from "../../../store/config/useConfigSelector";
import {
GLOBAL_ERROR_SORTING_CRITERION,
PAGE_SIZE
PAGE_SIZE,
ViewMode
} from "../../../store/errors/errorsSlice";
import { useErrorsSelector } from "../../../store/errors/useErrorsSelector";
import { useStore } from "../../../store/useStore";
import { isNumber } from "../../../typeGuards/isNumber";
import { isUndefined } from "../../../typeGuards/isUndefined";
import { FeatureFlag } from "../../../types";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
import { formatUnit } from "../../../utils/formatUnit";
import { OppositeArrowsIcon } from "../../common/icons/12px/OppositeArrowsIcon";
import { ChevronIcon } from "../../common/icons/16px/ChevronIcon";
import { CardsColoredIcon } from "../../common/icons/CardsColoredIcon";
import { Direction } from "../../common/icons/types";
import { NewPopover } from "../../common/NewPopover";
import { SearchInput } from "../../common/SearchInput";
import { NewButton } from "../../common/v3/NewButton";
Expand Down Expand Up @@ -58,8 +65,13 @@ export const GlobalErrorsList = () => {
);

const { environment, backendInfo } = useConfigSelector();
const isDismissEnabled = getFeatureFlagValue(
backendInfo,
FeatureFlag.IS_GLOBAL_ERROR_DISMISS_ENABLED
);

const {
globalErrorsViewMode: mode,
globalErrorsSearch: search,
globalErrorsSorting: sorting,
globalErrorsPage: page,
Expand All @@ -77,7 +89,8 @@ export const GlobalErrorsList = () => {
setGlobalErrorsSorting,
setGlobalErrorsPage,
resetGlobalErrors,
resetGlobalErrorsSelectedFilters
resetGlobalErrorsSelectedFilters,
setGlobalErrorsViewMode
} = useStore.getState();

const areGlobalErrorsFiltersEnabled = getFeatureFlagValue(
Expand Down Expand Up @@ -119,6 +132,7 @@ export const GlobalErrorsList = () => {
sortBy: sorting,
page,
pageSize: PAGE_SIZE,
dismissed: mode === ViewMode.OnlyDismissed,
...(areGlobalErrorsFiltersEnabled
? {
services: selectedFilters.services,
Expand All @@ -138,13 +152,14 @@ export const GlobalErrorsList = () => {
search,
sorting,
page,
mode,
areGlobalErrorsFiltersEnabled,
areGlobalErrorsCriticalityAndUnhandledFiltersEnabled,
selectedFilters.services,
selectedFilters.endpoints,
selectedFilters.errorTypes,
selectedFilters.criticalities,
selectedFilters.handlingTypes
selectedFilters.handlingTypes,
areGlobalErrorsCriticalityAndUnhandledFiltersEnabled
]
);

Expand All @@ -153,6 +168,29 @@ export const GlobalErrorsList = () => {
SetGlobalErrorsDataPayload
>(dataFetcherConfiguration, payload);

const isDismissalViewModeButtonVisible =
isDismissEnabled &&
data &&
!isUndefined(data.dismissedCount) &&
data.dismissedCount > 0;

// Refresh data after pin/unpin actions
useEffect(() => {
dispatcher.addActionListener(actions.SET_UNDISMISS_ERROR_RESULT, getData);
dispatcher.addActionListener(actions.SET_DISMISS_ERROR_RESULT, getData);

return () => {
dispatcher.removeActionListener(
actions.SET_UNDISMISS_ERROR_RESULT,
getData
);
dispatcher.removeActionListener(
actions.SET_DISMISS_ERROR_RESULT,
getData
);
};
}, [getData]);

useMount(() => {
toggleAnimations(false);

Expand Down Expand Up @@ -256,6 +294,16 @@ export const GlobalErrorsList = () => {
resetGlobalErrorsSelectedFilters();
};

const handleDismissalViewModeButtonClick = () => {
const newMode =
mode === ViewMode.All ? ViewMode.OnlyDismissed : ViewMode.All;
setGlobalErrorsViewMode(newMode);
};

const handleBackToAllInsightsButtonClick = () => {
setGlobalErrorsViewMode(ViewMode.All);
};

const handlePinStatusToggle = () => {
toggleAnimations(true);
};
Expand All @@ -265,6 +313,10 @@ export const GlobalErrorsList = () => {
getData();
};

const handleDismissalStatusChange = () => {
getData();
};

const areAnyFiltersApplied =
search ||
[
Expand All @@ -275,8 +327,40 @@ export const GlobalErrorsList = () => {
selectedFilters.handlingTypes
].some((x) => x.length > 0);

const renderDismissBtn = () => (
<NewButton
buttonType={"secondaryBorderless"}
icon={(props) => (
<s.DismissBtnIcon
{...props}
crossOut={mode !== ViewMode.OnlyDismissed}
$isDismissedMode={mode === ViewMode.OnlyDismissed}
/>
)}
onClick={handleDismissalViewModeButtonClick}
/>
);

return (
<s.Container>
{mode === ViewMode.OnlyDismissed && isNumber(data?.dismissedCount) && (
<s.ViewModeToolbarRow>
<s.BackToAllErrorsButton onClick={handleBackToAllInsightsButtonClick}>
<s.BackToAllErrorsButtonIconContainer>
<ChevronIcon
direction={Direction.LEFT}
size={16}
color={"currentColor"}
/>
</s.BackToAllErrorsButtonIconContainer>
Back to All Errors
</s.BackToAllErrorsButton>
<s.Description>
<s.Count>{data?.dismissedCount}</s.Count>
dismissed {formatUnit(data?.dismissedCount ?? 0, "error")}
</s.Description>
</s.ViewModeToolbarRow>
)}
{list ? (
<>
<s.ToolbarContainer>
Expand Down Expand Up @@ -314,17 +398,10 @@ export const GlobalErrorsList = () => {
onSourceLinkClick={handleErrorSourceLinkClick}
onPinStatusChange={handlePinStatusChange}
onPinStatusToggle={handlePinStatusToggle}
onDismissStatusChange={handleDismissalStatusChange}
/>
))}
</s.ListContainer>
<Pagination
itemsCount={totalCount}
page={page}
pageSize={pageSize}
onPageChange={handlePageChange}
extendedNavigation={true}
withDescription={true}
/>
</>
) : areAnyFiltersApplied ? (
<NewEmptyState
Expand All @@ -346,6 +423,20 @@ export const GlobalErrorsList = () => {
) : (
<NoDataEmptyState />
)}
{list.length > 0 ? (
<Pagination
itemsCount={totalCount}
page={page}
pageSize={pageSize}
onPageChange={handlePageChange}
extendedNavigation={true}
withDescription={true}
>
{isDismissalViewModeButtonVisible && renderDismissBtn()}
</Pagination>
) : isDismissalViewModeButtonVisible ? (
renderDismissBtn()
) : undefined}
</>
) : !environmentId ? (
<NoDataEmptyState />
Expand Down
103 changes: 103 additions & 0 deletions src/components/Errors/GlobalErrorsList/mockData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { SetGlobalErrorsDataPayload } from "./types";

export const DefaultErrorList: SetGlobalErrorsDataPayload = {
totalCount: 2,
list: [
{
id: "0021a8de-9134-11ef-8040-0242ac130002",
errorType: "java.lang.RuntimeException",
fromDisplayName: "CrashController.triggerException",
fromFullyQualifiedName:
"org.springframework.samples.petclinic.system.CrashController.triggerException",
fromCodeObjectId:
"method:org.springframework.samples.petclinic.system.CrashController$_$triggerException",
status: "Recent, 23 hours ago",
firstDetected: "2024-10-23T11:43:01.798918Z",
lastDetected: "2024-10-23T11:43:03.280678Z",
affectedEndpoints: [
{
displayName: "HTTP GET /oups",
service: "spring-petclinic",
spanCodeObjectId: "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /oups"
}
],
score: {
score: 70,
scoreParams: {
Occurrences: 0,
Trend: 0,
Recent: 20,
Unhandled: 50
}
},
unhandled: true
},
{
id: "00219416-9134-11ef-82aa-0242ac130002",
errorType:
"org.springframework.web.servlet.resource.NoResourceFoundException",
fromDisplayName: "ResourceHttpRequestHandler.handleRequest",
fromFullyQualifiedName:
"org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest",
fromCodeObjectId:
"method:org.springframework.web.servlet.resource.ResourceHttpRequestHandler$_$handleRequest",
status: "High number of errors",
firstDetected: "2024-10-23T11:43:00.505243Z",
lastDetected: "2024-10-23T12:36:16.873716Z",
affectedEndpoints: [
{
displayName: "HTTP GET /webjars/**",
service: "spring-petclinic",
spanCodeObjectId:
"span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**"
}
],
score: {
score: 45,
scoreParams: {
Occurrences: 25,
Trend: 0,
Recent: 20,
Unhandled: 0
}
},
unhandled: false
}
]
};

export const DismissedErrorList: SetGlobalErrorsDataPayload = {
totalCount: 2,
dismissedCount: 1,
list: [
{
id: "0021a8de-9134-11ef-8040-0242ac130002",
errorType: "java.lang.RuntimeException",
fromDisplayName: "CrashController.triggerException",
fromFullyQualifiedName:
"org.springframework.samples.petclinic.system.CrashController.triggerException",
fromCodeObjectId:
"method:org.springframework.samples.petclinic.system.CrashController$_$triggerException",
status: "Recent, 23 hours ago",
firstDetected: "2024-10-23T11:43:01.798918Z",
lastDetected: "2024-10-23T11:43:03.280678Z",
affectedEndpoints: [
{
displayName: "HTTP GET /oups",
service: "spring-petclinic",
spanCodeObjectId: "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /oups"
}
],
score: {
score: 70,
scoreParams: {
Occurrences: 0,
Trend: 0,
Recent: 20,
Unhandled: 50
}
},
unhandled: true
}
]
};
Loading
Loading