diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx
index 38e432ad3..3cbcf36ac 100644
--- a/src/components/Assets/AssetList/index.tsx
+++ b/src/components/Assets/AssetList/index.tsx
@@ -10,9 +10,7 @@ import { useStore } from "../../../store/useStore";
import { SCOPE_CHANGE_EVENTS } from "../../../types";
import { changeScope } from "../../../utils/actions/changeScope";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
-import { EmptyState } from "../../common/EmptyState";
import { Menu } from "../../common/Menu";
-import { NewCircleLoader } from "../../common/NewCircleLoader";
import { Pagination } from "../../common/Pagination";
import { Popover } from "../../common/Popover";
import { PopoverContent } from "../../common/Popover/PopoverContent";
@@ -20,6 +18,7 @@ import { PopoverTrigger } from "../../common/Popover/PopoverTrigger";
import { ChevronIcon } from "../../common/icons/ChevronIcon";
import { SortIcon } from "../../common/icons/SortIcon";
import { Direction } from "../../common/icons/types";
+import { EmptyState } from "../EmptyState";
import { actions } from "../actions";
import { trackingEvents } from "../tracking";
import { checkIfAnyFiltersApplied, getAssetTypeInfo } from "../utils";
@@ -292,7 +291,7 @@ export const AssetList = ({
const renderContent = () => {
if (isInitialLoading) {
- return } />;
+ return ;
}
return entries.length > 0 ? (
@@ -329,20 +328,14 @@ export const AssetList = ({
/>
>
+ ) : areAnyFiltersApplied ? (
+ search.length > 0 ? (
+
+ ) : (
+
+ )
) : (
-
- {areAnyFiltersApplied ? (
- <>
- It seems there are no assets matching your selected filters at the
- moment
- >
- ) : (
- <>
- Not seeing your data here? Maybe you're missing some
- instrumentation!
- >
- )}
-
+
);
};
diff --git a/src/components/Assets/AssetList/styles.ts b/src/components/Assets/AssetList/styles.ts
index 5518f331f..8bb11c97c 100644
--- a/src/components/Assets/AssetList/styles.ts
+++ b/src/components/Assets/AssetList/styles.ts
@@ -171,22 +171,6 @@ export const List = styled.ul`
height: 100%;
`;
-export const NoDataText = styled.span`
- padding: 10px;
- font-weight: 500;
- font-size: 14px;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#828797";
- case "dark":
- case "dark-jetbrains":
- return "#9b9b9b";
- }
- }};
-`;
-
export const Footer = styled.div`
display: flex;
justify-content: space-between;
diff --git a/src/components/Assets/AssetTypeList/index.tsx b/src/components/Assets/AssetTypeList/index.tsx
index a932b23c0..a76d6493b 100644
--- a/src/components/Assets/AssetTypeList/index.tsx
+++ b/src/components/Assets/AssetTypeList/index.tsx
@@ -12,9 +12,8 @@ import { isString } from "../../../typeGuards/isString";
import { SCOPE_CHANGE_EVENTS } from "../../../types";
import { changeScope } from "../../../utils/actions/changeScope";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
-import { ChildIcon } from "../../common/icons/30px/ChildIcon";
import type { ViewMode } from "../AssetsViewScopeConfiguration/types";
-import { NoDataMessage } from "../NoDataMessage";
+import { EmptyState } from "../EmptyState";
import { actions } from "../actions";
import { trackingEvents } from "../tracking";
import { checkIfAnyFiltersApplied, getAssetTypeInfo } from "../utils";
@@ -210,50 +209,42 @@ export const AssetTypeList = ({
};
if (isInitialLoading) {
- return ;
+ return ;
}
if (data?.assetCategories.every((x) => x.count === 0)) {
if (areAnyFiltersApplied) {
- return ;
+ if (search.length > 0) {
+ return ;
+ }
+ return ;
}
if (!scope) {
- return ;
+ return ;
}
if (showNoDataWithParents && data.parents) {
return (
-
-
-
-
- There are no child assets under this asset. You can try
-
-
- browsing its parent spans to continue to explore the trace.
-
-
- {data.parents.map((x) => (
- handleAssetLinkClick(x.spanCodeObjectId)}
- >
- {x.displayName}
-
- ))}
- >
- }
- />
-
+
+ {data.parents.map((x) => (
+ handleAssetLinkClick(x.spanCodeObjectId)}
+ >
+ {x.displayName}
+
+ ))}
+ >
+ }
+ />
);
}
- return ;
+ return ;
}
const assetTypeListItems = ASSET_TYPE_IDS.map((assetTypeId) => {
diff --git a/src/components/Assets/AssetTypeList/styles.ts b/src/components/Assets/AssetTypeList/styles.ts
index 5d6cca4a1..7edc22d8d 100644
--- a/src/components/Assets/AssetTypeList/styles.ts
+++ b/src/components/Assets/AssetTypeList/styles.ts
@@ -1,10 +1,6 @@
import styled from "styled-components";
-import {
- footnoteRegularTypography,
- subscriptRegularTypography
-} from "../../common/App/typographies";
+import { subscriptRegularTypography } from "../../common/App/typographies";
import { Link } from "../../common/v3/Link";
-import { NewEmptyState } from "../../common/v3/NewEmptyState";
export const List = styled.ul`
display: flex;
@@ -14,32 +10,6 @@ export const List = styled.ul`
margin: 0;
`;
-export const EmptyStateContainer = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- justify-content: center;
- height: 100%;
-`;
-
-export const EmptyStateTextContainer = styled.div`
- ${footnoteRegularTypography}
-
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 4px;
- padding-top: 4px;
- padding-bottom: 4px;
- color: ${({ theme }) => theme.colors.v3.text.tertiary};
-`;
-
-export const StyledEmptyState = styled(NewEmptyState)`
- flex-grow: 1;
- align-self: center;
-`;
-
export const ParentLink = styled(Link)`
text-decoration: underline;
${subscriptRegularTypography}
diff --git a/src/components/Assets/NoDataMessage/NoDataMessage.stories.tsx b/src/components/Assets/EmptyState/EmptyState.stories.tsx
similarity index 59%
rename from src/components/Assets/NoDataMessage/NoDataMessage.stories.tsx
rename to src/components/Assets/EmptyState/EmptyState.stories.tsx
index 0b7292e1d..41444290a 100644
--- a/src/components/Assets/NoDataMessage/NoDataMessage.stories.tsx
+++ b/src/components/Assets/EmptyState/EmptyState.stories.tsx
@@ -1,11 +1,10 @@
import type { Meta, StoryObj } from "@storybook/react";
-
-import { NoDataMessage } from ".";
+import { EmptyState } from ".";
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
-const meta: Meta = {
- title: "Assets/NoDataMessage",
- component: NoDataMessage,
+const meta: Meta = {
+ title: "Assets/EmptyState",
+ component: EmptyState,
parameters: {
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen"
@@ -16,27 +15,32 @@ export default meta;
type Story = StoryObj;
-// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
+export const UpdateRequired: Story = {
+ args: {
+ preset: "updateRequired"
+ }
+};
+
export const Loading: Story = {
args: {
- type: "loading"
+ preset: "loading"
}
};
export const NoDataYet: Story = {
args: {
- type: "noDataYet"
+ preset: "noDataYet"
}
};
-export const NoSearchResults: Story = {
+export const NoDataForAsset: Story = {
args: {
- type: "noSearchResults"
+ preset: "noDataForAsset"
}
};
-export const NoDataForAssetResults: Story = {
+export const NoSearchResults: Story = {
args: {
- type: "noDataForAsset"
+ preset: "noSearchResults"
}
};
diff --git a/src/components/Assets/EmptyState/index.tsx b/src/components/Assets/EmptyState/index.tsx
new file mode 100644
index 000000000..01481d7c2
--- /dev/null
+++ b/src/components/Assets/EmptyState/index.tsx
@@ -0,0 +1,94 @@
+import { actions as globalActions } from "../../../actions";
+import { trackingEvents as globalTrackingEvents } from "../../../trackingEvents";
+import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
+import { ChildIcon } from "../../common/icons/30px/ChildIcon";
+import { CardsColoredIcon } from "../../common/icons/CardsColoredIcon";
+import { EmptyState as CommonEmptyState } from "../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+import * as s from "./styles";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset) => {
+ const handleTroubleshootingLinkClick = () => {
+ sendUserActionTrackingEvent(
+ globalTrackingEvents.TROUBLESHOOTING_LINK_CLICKED,
+ {
+ origin: "assets"
+ }
+ );
+
+ window.sendMessageToDigma({
+ action: globalActions.OPEN_TROUBLESHOOTING_GUIDE
+ });
+ };
+
+ const content: Record = {
+ updateRequired: {
+ title: "We've added some new features.",
+ message:
+ "Please update the Digma Engine to the latest version using the action above to continue using Digma"
+ },
+ loading: {
+ icon: ,
+ title: "Fetching data"
+ },
+ noDataYet: {
+ icon: ,
+ title: "No data yet",
+ message:
+ "Trigger actions that call this application to learn more about its runtime behavior",
+ customContent: (
+
+ Not seeing your application data?
+
+ )
+ },
+ noDataForAsset: {
+ icon: ,
+ title: "No Assets",
+ message:
+ "No child assets found for the current scope. Add more observability to track internal functions not currently tracked."
+ },
+ noSearchResults: {
+ icon: ,
+ title: "No results",
+ message: "Check spelling or try to search something else."
+ },
+ noChildAssets: {
+ icon: ,
+ title: "No Child Assets",
+ message:
+ "No child assets found for this asset. You can try\n browsing its parent spans to continue to explore the trace."
+ },
+ noFilteredData: {
+ icon: ,
+ title: "No results",
+ message:
+ "It seems there are no assets matching your selected filters at the moment"
+ },
+ noData: {
+ message:
+ "Not seeing your data here? Maybe you're missing some instrumentation!"
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Assets/EmptyState/styles.ts b/src/components/Assets/EmptyState/styles.ts
new file mode 100644
index 000000000..a66e742f6
--- /dev/null
+++ b/src/components/Assets/EmptyState/styles.ts
@@ -0,0 +1,19 @@
+import styled from "styled-components";
+import { Link } from "../../common/Link";
+import { Spinner as CommonSpinner } from "../../common/v3/Spinner";
+
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
+`;
+
+export const TroubleshootingLink = styled(Link)`
+ color: ${({ theme }) => {
+ switch (theme.mode) {
+ case "light":
+ return "#7891d0";
+ case "dark":
+ case "dark-jetbrains":
+ return "#92affa";
+ }
+ }};
+`;
diff --git a/src/components/Assets/EmptyState/types.ts b/src/components/Assets/EmptyState/types.ts
new file mode 100644
index 000000000..502df26c7
--- /dev/null
+++ b/src/components/Assets/EmptyState/types.ts
@@ -0,0 +1,15 @@
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+
+export type EmptyStatePreset =
+ | "updateRequired"
+ | "loading"
+ | "noDataYet"
+ | "noDataForAsset"
+ | "noSearchResults"
+ | "noChildAssets"
+ | "noFilteredData"
+ | "noData";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
+}
diff --git a/src/components/Assets/NoDataMessage/index.tsx b/src/components/Assets/NoDataMessage/index.tsx
deleted file mode 100644
index 929e02c7d..000000000
--- a/src/components/Assets/NoDataMessage/index.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import { actions as globalActions } from "../../../actions";
-import { trackingEvents as globalTrackingEvents } from "../../../trackingEvents";
-import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
-import { EmptyState } from "../../common/EmptyState";
-import { NewCircleLoader } from "../../common/NewCircleLoader";
-import { CardsIcon } from "../../common/icons/CardsIcon";
-import * as s from "./styles";
-import type { NoDataMessageProps } from "./types";
-
-export const NoDataMessage = ({ type }: NoDataMessageProps) => {
- const handleTroubleshootingLinkClick = () => {
- sendUserActionTrackingEvent(
- globalTrackingEvents.TROUBLESHOOTING_LINK_CLICKED,
- {
- origin: "assets"
- }
- );
-
- window.sendMessageToDigma({
- action: globalActions.OPEN_TROUBLESHOOTING_GUIDE
- });
- };
-
- let content: JSX.Element | null = null;
-
- switch (type) {
- case "loading":
- content = ;
- break;
- case "noDataYet":
- content = (
-
-
- Trigger actions that call this application to learn more about
- its runtime behavior
-
-
- Not seeing your application data?
-
- >
- }
- />
- );
- break;
- case "noDataForAsset":
- content = (
-
-
- No child assets found for the current scope. Add more
- observability to track internal functions not currently tracked.
-
- >
- }
- />
- );
- break;
- case "noSearchResults":
- content = (
-
- It seems there are no assets matching your selected filters at the
- moment
-
- }
- />
- );
- }
-
- return {content};
-};
diff --git a/src/components/Assets/NoDataMessage/styles.ts b/src/components/Assets/NoDataMessage/styles.ts
deleted file mode 100644
index f4542a328..000000000
--- a/src/components/Assets/NoDataMessage/styles.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import styled from "styled-components";
-import { Link } from "../../common/Link";
-
-export const NoDataContainer = styled.div`
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
-`;
-
-export const EmptyStateDescription = styled.span`
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#788ca9";
- case "dark":
- case "dark-jetbrains":
- return "#7c7c94";
- }
- }};
-`;
-
-export const TroubleshootingLink = styled(Link)`
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#7891d0";
- case "dark":
- case "dark-jetbrains":
- return "#92affa";
- }
- }};
-`;
diff --git a/src/components/Assets/NoDataMessage/types.ts b/src/components/Assets/NoDataMessage/types.ts
deleted file mode 100644
index ddfedc77f..000000000
--- a/src/components/Assets/NoDataMessage/types.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface NoDataMessageProps {
- type: "loading" | "noDataYet" | "noSearchResults" | "noDataForAsset";
-}
diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx
index 82d7e24dd..0a20b43aa 100644
--- a/src/components/Assets/index.tsx
+++ b/src/components/Assets/index.tsx
@@ -10,7 +10,6 @@ import { useStore } from "../../store/useStore";
import { isUndefined } from "../../typeGuards/isUndefined";
import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent";
import { useHistory } from "../Main/useHistory";
-import { EmptyState } from "../common/EmptyState";
import { SearchInput } from "../common/SearchInput";
import { RefreshIcon } from "../common/icons/16px/RefreshIcon";
import { NewIconButton } from "../common/v3/NewIconButton";
@@ -20,7 +19,7 @@ import { AssetTypeList } from "./AssetTypeList";
import type { AssetCategoriesData } from "./AssetTypeList/types";
import { AssetsFilter } from "./AssetsFilter";
import { AssetsViewScopeConfiguration } from "./AssetsViewScopeConfiguration";
-import { NoDataMessage } from "./NoDataMessage";
+import { EmptyState } from "./EmptyState";
import * as s from "./styles";
import { trackingEvents } from "./tracking";
import type { DataRefresher } from "./types";
@@ -149,27 +148,15 @@ export const Assets = () => {
const renderContent = () => {
if (isBackendUpgradeMessageVisible) {
- return (
-
- We've added some new features.
-
- Please update the Digma Engine to the latest version using the
- action above to continue using Digma
-
-
- }
- />
- );
+ return ;
}
if (!environments?.length) {
- return ;
+ return ;
}
if (!filters && showAssetsHeaderToolBox) {
- return ;
+ return ;
}
if (!selectedAssetTypeId) {
diff --git a/src/components/Assets/styles.ts b/src/components/Assets/styles.ts
index cbdb8e5b4..3491160a5 100644
--- a/src/components/Assets/styles.ts
+++ b/src/components/Assets/styles.ts
@@ -28,10 +28,3 @@ export const HeaderItem = styled.div`
flex-shrink: 0;
height: 36px;
`;
-
-export const UpgradeMessage = styled.div`
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 8px;
-`;
diff --git a/src/components/Dashboard/MetricsReport/EmptyState/EmptyState.stories.tsx b/src/components/Dashboard/MetricsReport/EmptyState/EmptyState.stories.tsx
index 3be674f98..2e25176db 100644
--- a/src/components/Dashboard/MetricsReport/EmptyState/EmptyState.stories.tsx
+++ b/src/components/Dashboard/MetricsReport/EmptyState/EmptyState.stories.tsx
@@ -19,24 +19,24 @@ type Story = StoryObj;
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
export const NoData: Story = {
args: {
- type: "noData"
+ preset: "noData"
}
};
export const NoEndpoints: Story = {
args: {
- type: "noEndpoints"
+ preset: "noEndpoints"
}
};
export const NoServices: Story = {
args: {
- type: "noServices"
+ preset: "noServices"
}
};
export const Loading: Story = {
args: {
- type: "loading"
+ preset: "loading"
}
};
diff --git a/src/components/Dashboard/MetricsReport/EmptyState/index.tsx b/src/components/Dashboard/MetricsReport/EmptyState/index.tsx
index 44d51a619..818327a82 100644
--- a/src/components/Dashboard/MetricsReport/EmptyState/index.tsx
+++ b/src/components/Dashboard/MetricsReport/EmptyState/index.tsx
@@ -1,27 +1,22 @@
-import type { DefaultTheme } from "styled-components";
-import { useTheme } from "styled-components";
import { CrossCircleIcon } from "../../../common/icons/20px/CrossCircleIcon";
-import { PetalsIcon } from "../../../common/icons/32px/PetalsIcon";
import { CardsColoredIcon } from "../../../common/icons/CardsColoredIcon";
+import { EmptyState as CommonEmptyState } from "../../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../../common/v3/EmptyState/types";
import { NewButton } from "../../../common/v3/NewButton";
import * as s from "./styles";
-import type {
- EmptyStateContent,
- EmptyStateProps,
- EmptyStateType
-} from "./types";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
-const getContent = (type: EmptyStateType, theme: DefaultTheme) => {
+const getPresetContent = (preset: EmptyStatePreset) => {
const handleRefreshButtonClick = () => {
window.location.reload();
};
- const content: Record = {
+ const content: Record = {
noData: {
title: "No available data",
message:
"Make sure you have at least one active environment to fetch data from",
- icon: ,
+ icon: ,
customContent: (
{
noServices: {
title: "No Results",
message: "No services recorded for this environment",
- icon:
+ icon:
},
noEndpoints: {
title: "No Results",
message: "No entry points recorded for this environment",
- icon:
+ icon:
},
loading: {
title: "Fetching results",
message: "Updating the results list may take a few moments",
- icon:
+ icon:
}
};
- return content[type];
+ return content[preset];
};
-export const EmptyState = ({ type }: EmptyStateProps) => {
- const theme = useTheme();
- const content = getContent(type, theme);
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
- return content ? (
-
-
- {content.icon}
-
- {content.title}
- {content.message}
-
- {content.customContent}
-
-
- ) : null;
+ return ;
};
diff --git a/src/components/Dashboard/MetricsReport/EmptyState/styles.ts b/src/components/Dashboard/MetricsReport/EmptyState/styles.ts
index 59a2da6f8..7884e2909 100644
--- a/src/components/Dashboard/MetricsReport/EmptyState/styles.ts
+++ b/src/components/Dashboard/MetricsReport/EmptyState/styles.ts
@@ -1,44 +1,6 @@
import styled from "styled-components";
-import {
- bodySemiboldTypography,
- footnoteRegularTypography
-} from "../../../common/App/typographies";
+import { Spinner as CommonSpinner } from "../../../common/v3/Spinner";
-export const Container = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- flex-grow: 1;
-`;
-
-export const ContentContainer = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- max-width: 210px;
-`;
-
-export const TextContainer = styled.div`
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 4px;
- ${footnoteRegularTypography}
- color: ${({ theme }) => theme.colors.v3.text.tertiary};
-`;
-
-export const IconContainer = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- width: 50px;
- height: 50px;
- border-radius: 50%;
- background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
-`;
-
-export const Title = styled.span`
- ${bodySemiboldTypography}
- color: ${({ theme }) => theme.colors.v3.text.primary};
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
`;
diff --git a/src/components/Dashboard/MetricsReport/EmptyState/types.ts b/src/components/Dashboard/MetricsReport/EmptyState/types.ts
index 5b75bb1e6..dd74a7022 100644
--- a/src/components/Dashboard/MetricsReport/EmptyState/types.ts
+++ b/src/components/Dashboard/MetricsReport/EmptyState/types.ts
@@ -1,18 +1,11 @@
-import type { ReactNode } from "react";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../../common/v3/EmptyState/types";
-export type EmptyStateType =
+export type EmptyStatePreset =
| "noEndpoints"
| "noServices"
| "noData"
| "loading";
-export interface EmptyStateProps {
- type: EmptyStateType;
-}
-
-export interface EmptyStateContent {
- title: string;
- message: string;
- icon: ReactNode;
- customContent?: ReactNode;
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
}
diff --git a/src/components/Dashboard/MetricsReport/index.tsx b/src/components/Dashboard/MetricsReport/index.tsx
index cc8e20a7d..16280f1b7 100644
--- a/src/components/Dashboard/MetricsReport/index.tsx
+++ b/src/components/Dashboard/MetricsReport/index.tsx
@@ -223,16 +223,16 @@ export const MetricsReport = () => {
(viewLevel === "services" && !servicesIssuesData) ||
(viewLevel === "endpoints" && !endpointsIssuesData)
) {
- return ;
+ return ;
}
if (data.length === 0) {
if (viewLevel === "services") {
- return ;
+ return ;
}
if (viewLevel === "endpoints") {
- return ;
+ return ;
}
}
@@ -274,10 +274,10 @@ export const MetricsReport = () => {
{renderContent()}
>
) : (
-
+
)
) : (
-
+
)}
diff --git a/src/components/Errors/EmptyState/EmptyState.stories.tsx b/src/components/Errors/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..d8962801c
--- /dev/null
+++ b/src/components/Errors/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,54 @@
+import type { Meta, StoryObj } from "@storybook/react";
+
+import { EmptyState } from ".";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "Errors/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
+export const NoData: Story = {
+ args: {
+ preset: "noData"
+ }
+};
+
+export const NoFilteredData: Story = {
+ args: {
+ preset: "noFilteredData"
+ }
+};
+
+export const NoSearchResults: Story = {
+ args: {
+ preset: "noSearchResults"
+ }
+};
+
+export const NoDismissedData: Story = {
+ args: {
+ preset: "noDismissedData"
+ }
+};
+
+export const SelectAsset: Story = {
+ args: {
+ preset: "selectAsset"
+ }
+};
+
+export const Loading: Story = {
+ args: {
+ preset: "loading"
+ }
+};
diff --git a/src/components/Errors/EmptyState/index.tsx b/src/components/Errors/EmptyState/index.tsx
new file mode 100644
index 000000000..56257f13c
--- /dev/null
+++ b/src/components/Errors/EmptyState/index.tsx
@@ -0,0 +1,63 @@
+import { ErrorIcon } from "../../common/icons/16px/ErrorIcon";
+import { CheckCircleIcon } from "../../common/icons/38px/CheckCircleIcon";
+import { CardsColoredIcon } from "../../common/icons/CardsColoredIcon";
+import { EmptyState as CommonEmptyState } from "../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+import * as s from "./styles";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset) => {
+ const content: Record = {
+ noData: {
+ icon: ,
+ title: "Good News!\nNo Errors Recorded Yet",
+ message:
+ "You should return to this page if any exceptions do occur to see more details."
+ },
+ noSearchResults: {
+ icon: ,
+ title: "No results",
+ message: "Check spelling or try to search something else."
+ },
+ noFilteredData: {
+ icon: ,
+ title: "No errors",
+ message:
+ "No data is available for the selected filters. Try resetting your filters."
+ },
+ noDismissedData: {
+ icon: ,
+ title: "No dismissed errors available"
+ },
+ selectAsset: {
+ icon: ,
+ title: "Select an asset to view errors",
+ message:
+ "The Errors tab shows details for\nexceptions for each Digma-tracked\nasset. See all tracked assets on the\nAssets page."
+ },
+ loading: {
+ icon: ,
+ title: "Fetching data"
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Errors/EmptyState/styles.ts b/src/components/Errors/EmptyState/styles.ts
new file mode 100644
index 000000000..af9498e4b
--- /dev/null
+++ b/src/components/Errors/EmptyState/styles.ts
@@ -0,0 +1,6 @@
+import styled from "styled-components";
+import { Spinner as CommonSpinner } from "../../common/v3/Spinner";
+
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
+`;
diff --git a/src/components/Errors/EmptyState/types.ts b/src/components/Errors/EmptyState/types.ts
new file mode 100644
index 000000000..52dce1137
--- /dev/null
+++ b/src/components/Errors/EmptyState/types.ts
@@ -0,0 +1,13 @@
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+
+export type EmptyStatePreset =
+ | "noData"
+ | "noSearchResults"
+ | "noFilteredData"
+ | "noDismissedData"
+ | "selectAsset"
+ | "loading";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
+}
diff --git a/src/components/Errors/ErrorDetails/index.tsx b/src/components/Errors/ErrorDetails/index.tsx
index 330bc8b46..c843a6a30 100644
--- a/src/components/Errors/ErrorDetails/index.tsx
+++ b/src/components/Errors/ErrorDetails/index.tsx
@@ -1,9 +1,8 @@
import { useMemo } from "react";
import type { DataFetcherConfiguration } from "../../../hooks/useFetchData";
import { useFetchData } from "../../../hooks/useFetchData";
-import { NewCircleLoader } from "../../common/NewCircleLoader";
import { actions } from "../actions";
-import { EmptyStateContainer } from "../styles";
+import { EmptyState } from "../EmptyState";
import { ErrorDetailsCardContent } from "./ErrorDetailsCardContent";
import { ErrorDetailsCardHeader } from "./ErrorDetailsCardHeader";
import * as s from "./styles";
@@ -35,11 +34,7 @@ export const ErrorDetails = ({ id, onGoToAllErrors }: ErrorDetailsProps) => {
if (!data) {
// TODO: replace with skeletons
- return (
-
-
-
- );
+ return ;
}
const handleGoBack = () => {
diff --git a/src/components/Errors/ErrorsList/index.tsx b/src/components/Errors/ErrorsList/index.tsx
index 461940060..84c17d154 100644
--- a/src/components/Errors/ErrorsList/index.tsx
+++ b/src/components/Errors/ErrorsList/index.tsx
@@ -2,11 +2,9 @@ import { useMemo } from "react";
import type { DataFetcherConfiguration } from "../../../hooks/useFetchData";
import { useFetchData } from "../../../hooks/useFetchData";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
-import { NewCircleLoader } from "../../common/NewCircleLoader";
+import { EmptyState } from "../EmptyState";
import { ErrorCard } from "../ErrorCard";
-import { NoDataEmptyState } from "../NoDataEmptyState";
import { actions } from "../actions";
-import { EmptyStateContainer } from "../styles";
import { trackingEvents } from "../tracking";
import type { GetErrorsDataPayload, SetErrorsDataPayload } from "../types";
import * as s from "./styles";
@@ -39,15 +37,11 @@ export const ErrorsList = ({
if (!data) {
// TODO: replace with skeletons
- return (
-
-
-
- );
+ return ;
}
if (data.errors.length === 0) {
- return ;
+ return ;
}
const handleErrorCardClick = (errorId: string) => {
diff --git a/src/components/Errors/GlobalErrorsList/index.tsx b/src/components/Errors/GlobalErrorsList/index.tsx
index 81cdd82a5..5920bd3f1 100644
--- a/src/components/Errors/GlobalErrorsList/index.tsx
+++ b/src/components/Errors/GlobalErrorsList/index.tsx
@@ -21,19 +21,17 @@ import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActi
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";
-import { NewEmptyState } from "../../common/v3/NewEmptyState";
import { Pagination } from "../../common/v3/Pagination";
import { useHistory } from "../../Main/useHistory";
import { MenuList } from "../../Navigation/common/MenuList";
import { Popup } from "../../Navigation/common/Popup";
import { actions } from "../actions";
+import { EmptyState } from "../EmptyState";
import { NewErrorCard } from "../NewErrorCard";
-import { NoDataEmptyState } from "../NoDataEmptyState";
import { trackingEvents } from "../tracking";
import { DaysFilter } from "./DaysFilter";
import { GlobalErrorsFilters } from "./GlobalErrorsFilters";
@@ -430,24 +428,21 @@ export const GlobalErrorsList = () => {
))}
) : areAnyFiltersApplied ? (
-
-
- No data is available for the selected filters. Try resetting
- your filters.
-
+ search.length > 0 ? (
+
+ ) : (
+
-
- }
- />
+ }
+ />
+ )
) : (
-
+
)}
{list.length > 0 ? (
{
) : undefined}
>
) : !environmentId ? (
-
+
) : null}
);
diff --git a/src/components/Errors/GlobalErrorsList/styles.ts b/src/components/Errors/GlobalErrorsList/styles.ts
index e21d1a761..104d1dfab 100644
--- a/src/components/Errors/GlobalErrorsList/styles.ts
+++ b/src/components/Errors/GlobalErrorsList/styles.ts
@@ -1,6 +1,5 @@
import styled from "styled-components";
import {
- footnoteRegularTypography,
subscriptMediumTypography,
subscriptRegularTypography
} from "../../common/App/typographies";
@@ -73,25 +72,6 @@ export const ListContainer = styled(AutoAnimatedContainer)`
overflow: auto;
`;
-export const EmptyStateContainer = styled.div`
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
-`;
-
-export const EmptyStateContent = styled.div`
- ${footnoteRegularTypography}
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- gap: 8px;
- max-width: 210px;
- color: ${({ theme }) => theme.colors.v3.text.tertiary};
-`;
-
export const ButtonIconContainer = styled.div`
color: ${({ theme }) => theme.colors.v3.icon.tertiary};
`;
diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx
index 40762f9c0..158b745e7 100644
--- a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx
+++ b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx
@@ -17,7 +17,6 @@ import { useConfigSelector } from "../../../../store/config/useConfigSelector";
import { isNumber } from "../../../../typeGuards/isNumber";
import { measureTextWidth } from "../../../../utils/measureTextWidth";
import { HistogramIcon } from "../../../common/icons/30px/HistogramIcon";
-import { PetalsIcon } from "../../../common/icons/32px/PetalsIcon";
import { actions } from "../../actions";
import * as s from "./styles";
import type {
@@ -205,7 +204,7 @@ export const OccurrenceChart = ({
) : (
-
+
Loading
diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts b/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts
index c58d0a5ca..d5c1e13a8 100644
--- a/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts
+++ b/src/components/Errors/NewErrorCard/OccurrenceChart/styles.ts
@@ -1,5 +1,6 @@
import styled from "styled-components";
import { bodySemiboldTypography } from "../../../common/App/typographies";
+import { Spinner as CommonSpinner } from "../../../common/v3/Spinner";
import { Tooltip } from "../../../common/v3/Tooltip/styles";
export const HEIGHT = 208; // in pixels
@@ -56,3 +57,7 @@ export const EmptyStateIconContainer = styled.div`
background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
color: ${({ theme }) => theme.colors.v3.surface.gray};
`;
+
+export const Spinner = styled(CommonSpinner)`
+ color: currentcolor;
+`;
diff --git a/src/components/Errors/NewErrorCard/hooks/types.ts b/src/components/Errors/NewErrorCard/hooks/types.ts
index 8b92d0c1a..6544254e0 100644
--- a/src/components/Errors/NewErrorCard/hooks/types.ts
+++ b/src/components/Errors/NewErrorCard/hooks/types.ts
@@ -1,4 +1,4 @@
-export interface DismissPayload {
+export interface DismissResultPayload {
id: string;
status: "success" | "failure";
error?: {
@@ -6,7 +6,7 @@ export interface DismissPayload {
};
}
-export interface UndismissPayload {
+export interface UndismissResultPayload {
id: string;
status: "success" | "failure";
error?: {
diff --git a/src/components/Errors/NewErrorCard/hooks/useDismissal.ts b/src/components/Errors/NewErrorCard/hooks/useDismissal.ts
index d30347286..c9c0632f6 100644
--- a/src/components/Errors/NewErrorCard/hooks/useDismissal.ts
+++ b/src/components/Errors/NewErrorCard/hooks/useDismissal.ts
@@ -1,19 +1,19 @@
import { useEffect, useState } from "react";
import { useAction } from "../../../../hooks/useAction";
import { actions } from "../../actions";
-import type { DismissPayload, UndismissPayload } from "./types";
+import type { DismissResultPayload, UndismissResultPayload } from "./types";
export const useDismissal = (id: string) => {
const [data, setData] = useState<{
action: string;
- payload: DismissPayload | UndismissPayload;
+ payload: DismissResultPayload | UndismissResultPayload;
} | null>(null);
const {
isOperationInProgress: isDismissInProgress,
execute: dismiss,
data: dismissData
- } = useAction<{ id: string }, DismissPayload>(
+ } = useAction<{ id: string }, DismissResultPayload>(
actions.DISMISS_ERROR,
actions.SET_DISMISS_ERROR_RESULT,
{
@@ -25,7 +25,7 @@ export const useDismissal = (id: string) => {
isOperationInProgress: isUndismissInProgress,
execute: undismiss,
data: undismissData
- } = useAction<{ id: string }, UndismissPayload>(
+ } = useAction<{ id: string }, UndismissResultPayload>(
actions.UNDISMISS_ERROR,
actions.SET_UNDISMISS_ERROR_RESULT,
{
diff --git a/src/components/Errors/NewErrorCard/index.tsx b/src/components/Errors/NewErrorCard/index.tsx
index 513b5b5f5..c9ee072b3 100644
--- a/src/components/Errors/NewErrorCard/index.tsx
+++ b/src/components/Errors/NewErrorCard/index.tsx
@@ -3,6 +3,9 @@ import { CSSTransition } from "react-transition-group";
import { getFeatureFlagValue } from "../../../featureFlags";
import { usePrevious } from "../../../hooks/usePrevious";
import { useConfigSelector } from "../../../store/config/useConfigSelector";
+import { ViewMode } from "../../../store/errors/errorsSlice";
+import { useErrorsSelector } from "../../../store/errors/useErrorsSelector";
+import { useStore } from "../../../store/useStore";
import { FeatureFlag } from "../../../types";
import { changeScope } from "../../../utils/actions/changeScope";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
@@ -16,6 +19,7 @@ import { PinFillIcon } from "../../common/icons/16px/PinFillIcon";
import { PinIcon } from "../../common/icons/16px/PinIcon";
import { NewIconButton } from "../../common/v3/NewIconButton";
import { Tooltip } from "../../common/v3/Tooltip";
+import { actions } from "../actions";
import { TimestampKeyValue } from "../ErrorCard/TimestampKeyValue";
import { usePinning } from "../GlobalErrorsList/usePinning";
import { getTagType, HIGH_SEVERITY_SCORE_THRESHOLD } from "../Score";
@@ -38,6 +42,8 @@ export const NewErrorCard = ({
const [isHistogramVisible, setIsHistogramVisible] = useState(false);
const chartContainerRef = useRef(null);
const { backendInfo } = useConfigSelector();
+ const { globalErrorsList } = useErrorsSelector();
+ const { setGlobalErrorsViewMode } = useStore.getState();
const [isPinned, setIsPinned] = useState(Boolean(data.pinnedAt));
const isOccurrenceChartEnabled = getFeatureFlagValue(
@@ -113,8 +119,22 @@ export const NewErrorCard = ({
dismissalData?.payload.status === "success"
) {
onDismissStatusChange(dismissalData.payload.id);
+
+ if (
+ dismissalData.action === actions.SET_UNDISMISS_ERROR_RESULT &&
+ globalErrorsList?.length === 1 &&
+ globalErrorsList[0].id === dismissalData.payload.id
+ ) {
+ setGlobalErrorsViewMode(ViewMode.All);
+ }
}
- }, [dismissalData, onDismissStatusChange, previousDismissalData]);
+ }, [
+ dismissalData,
+ globalErrorsList,
+ onDismissStatusChange,
+ previousDismissalData,
+ setGlobalErrorsViewMode
+ ]);
useEffect(() => {
if (
diff --git a/src/components/Errors/NoDataEmptyState/index.tsx b/src/components/Errors/NoDataEmptyState/index.tsx
deleted file mode 100644
index e7195d959..000000000
--- a/src/components/Errors/NoDataEmptyState/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { CheckCircleIcon } from "../../common/icons/38px/CheckCircleIcon";
-import { EmptyStateContainer, EmptyStateTextContainer } from "../styles";
-import * as s from "./styles";
-
-export const NoDataEmptyState = () => (
-
-
-
-
-
-
- Good News!
- No Errors Recorded Yet
-
- You should return to this page if any exceptions do occur to see more
- details.
-
-
-);
diff --git a/src/components/Errors/NoDataEmptyState/styles.ts b/src/components/Errors/NoDataEmptyState/styles.ts
deleted file mode 100644
index f8415481e..000000000
--- a/src/components/Errors/NoDataEmptyState/styles.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import styled from "styled-components";
-import { bodySemiboldTypography } from "../../common/App/typographies";
-
-export const EmptyStateIconContainer = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- width: 50px;
- height: 50px;
- border-radius: 50%;
- color: ${({ theme }) => theme.colors.v3.icon.secondary};
- background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
-`;
-
-export const EmptyStateTitle = styled.div`
- ${bodySemiboldTypography}
-
- display: flex;
- flex-direction: column;
- color: ${({ theme }) => theme.colors.v3.text.primary};
-`;
diff --git a/src/components/Errors/index.tsx b/src/components/Errors/index.tsx
index 2b2726a68..54c3d47c1 100644
--- a/src/components/Errors/index.tsx
+++ b/src/components/Errors/index.tsx
@@ -11,11 +11,10 @@ import { trackingEvents as globalEvents } from "../../trackingEvents";
import { isUndefined } from "../../typeGuards/isUndefined";
import { FeatureFlag } from "../../types";
import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent";
-import { ErrorIcon } from "../common/icons/16px/ErrorIcon";
import { NewButton } from "../common/v3/NewButton";
-import { NewEmptyState } from "../common/v3/NewEmptyState";
import { useHistory } from "../Main/useHistory";
import { TAB_IDS } from "../Navigation/Tabs/types";
+import { EmptyState } from "./EmptyState";
import { ErrorDetails } from "./ErrorDetails";
import type { ShowOnlyWorkspaceErrorStackTraceItemsPayload } from "./ErrorDetails/ErrorDetailsCardContent/FlowStack/types";
import { ErrorsList } from "./ErrorsList";
@@ -163,27 +162,16 @@ export const Errors = () => {
}
return (
-
-
-
- The Errors tab shows details for
- exceptions for each Digma-tracked
- asset. See all tracked assets on the
- Assets page.
-
-
- >
- }
- />
-
+
+ }
+ />
);
}
diff --git a/src/components/Errors/styles.ts b/src/components/Errors/styles.ts
index f0793f455..459103c5b 100644
--- a/src/components/Errors/styles.ts
+++ b/src/components/Errors/styles.ts
@@ -1,30 +1,7 @@
import styled from "styled-components";
-import { footnoteRegularTypography } from "../common/App/typographies";
export const Container = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
-
-export const EmptyStateTextContainer = styled.div`
- ${footnoteRegularTypography}
-
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 4px;
- padding-top: 4px;
- padding-bottom: 4px;
- color: ${({ theme }) => theme.colors.v3.text.tertiary};
-`;
-
-export const EmptyStateContainer = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 8px;
- justify-content: center;
- flex-grow: 1;
- align-self: center;
-`;
diff --git a/src/components/Insights/EmptyState/EmptyState.stories.tsx b/src/components/Insights/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..2fde237db
--- /dev/null
+++ b/src/components/Insights/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,52 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { EmptyState } from ".";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "Insights/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const NothingToShow: Story = {
+ args: {
+ preset: "nothingToShow"
+ }
+};
+
+export const NoDataYet: Story = {
+ args: {
+ preset: "noDataYet"
+ }
+};
+
+export const Loading: Story = {
+ args: {
+ preset: "loading"
+ }
+};
+
+export const NoInsights: Story = {
+ args: {
+ preset: "noInsights"
+ }
+};
+
+export const Processing: Story = {
+ args: {
+ preset: "processing"
+ }
+};
+
+export const NoObservability: Story = {
+ args: {
+ preset: "noObservability"
+ }
+};
diff --git a/src/components/Insights/EmptyState/index.tsx b/src/components/Insights/EmptyState/index.tsx
new file mode 100644
index 000000000..886448798
--- /dev/null
+++ b/src/components/Insights/EmptyState/index.tsx
@@ -0,0 +1,104 @@
+import { useTheme, type DefaultTheme } from "styled-components";
+import { actions as globalActions } from "../../../actions";
+import { SLACK_WORKSPACE_URL } from "../../../constants";
+import { trackingEvents as globalTrackingEvents } from "../../../trackingEvents";
+import { openURLInDefaultBrowser } from "../../../utils/actions/openURLInDefaultBrowser";
+import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
+import { getThemeKind } from "../../common/App/styles";
+import { DocumentWithMagnifierIcon } from "../../common/icons/DocumentWithMagnifierIcon";
+import { LightBulbSmallCrossedIcon } from "../../common/icons/LightBulbSmallCrossedIcon";
+import { LightBulbSmallIcon } from "../../common/icons/LightBulbSmallIcon";
+import { OpenTelemetryLogoCrossedSmallIcon } from "../../common/icons/OpenTelemetryLogoCrossedSmallIcon";
+import { SlackLogoIcon } from "../../common/icons/SlackLogoIcon";
+import { EmptyState as CommonEmptyState } from "../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+import * as s from "./styles";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset, theme: DefaultTheme) => {
+ const themeKind = getThemeKind(theme);
+
+ const handleSlackLinkClick = () => {
+ openURLInDefaultBrowser(SLACK_WORKSPACE_URL);
+ };
+
+ const handleTroubleshootingLinkClick = () => {
+ sendUserActionTrackingEvent(
+ globalTrackingEvents.TROUBLESHOOTING_LINK_CLICKED,
+ {
+ origin: "insights"
+ }
+ );
+
+ window.sendMessageToDigma({
+ action: globalActions.OPEN_TROUBLESHOOTING_GUIDE
+ });
+ };
+
+ const content: Record = {
+ nothingToShow: {
+ icon: ,
+ title: "Nothing to show",
+ message:
+ "Navigate to any code file in your workspace,\n or click a recent activity,\n to see runtime data and insights here.",
+ customContent: (
+
+
+ Join Our Slack Channel for Support
+
+ )
+ },
+ noDataYet: {
+ title: "No data yet",
+ message:
+ "Trigger actions that call this application to learn more about its runtime behavior",
+ customContent: (
+
+ Not seeing your application data?
+
+ )
+ },
+ loading: {
+ icon: ,
+ title: "Fetching data"
+ },
+ noInsights: {
+ icon: ,
+ title: "No insights"
+ },
+ processing: {
+ icon: ,
+ title: "Processing insights..."
+ },
+ noObservability: {
+ icon: (
+
+ ),
+ title: "No observability",
+ message:
+ " Add an annotation to observe this method and collect data about its runtime behavior"
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const theme = useTheme();
+
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset, theme) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Insights/EmptyState/styles.ts b/src/components/Insights/EmptyState/styles.ts
new file mode 100644
index 000000000..08a1f51aa
--- /dev/null
+++ b/src/components/Insights/EmptyState/styles.ts
@@ -0,0 +1,19 @@
+import styled from "styled-components";
+import { Link as CommonLink } from "../../common/Link";
+import { Spinner as CommonSpinner } from "../../common/v3/Spinner";
+import { Link } from "../styles";
+
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
+`;
+
+export const TroubleshootingLink = styled(CommonLink)`
+ font-size: 14px;
+ text-decoration: underline;
+`;
+
+export const SlackLink = styled(Link)`
+ display: flex;
+ align-items: center;
+ gap: 4px;
+`;
diff --git a/src/components/Insights/EmptyState/types.ts b/src/components/Insights/EmptyState/types.ts
new file mode 100644
index 000000000..6c842efce
--- /dev/null
+++ b/src/components/Insights/EmptyState/types.ts
@@ -0,0 +1,13 @@
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+
+export type EmptyStatePreset =
+ | "nothingToShow"
+ | "noDataYet"
+ | "loading"
+ | "noInsights"
+ | "processing"
+ | "noObservability";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
+}
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/EmptyState.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..56a1766b9
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,58 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { EmptyState } 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/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const NoFilteredData: Story = {
+ args: {
+ preset: "noFilteredData"
+ }
+};
+
+export const NoSearchResults: Story = {
+ args: {
+ preset: "noSearchResults"
+ }
+};
+
+export const NoDismissedData: Story = {
+ args: {
+ preset: "noDismissedData"
+ }
+};
+
+export const NoInsightsYet: Story = {
+ args: {
+ preset: "noInsightsYet"
+ }
+};
+
+export const NoSpanDataYet: Story = {
+ args: {
+ preset: "noSpanDataYet"
+ }
+};
+
+export const AnalyticsSelectAsset: Story = {
+ args: {
+ preset: "analyticsSelectAsset"
+ }
+};
+
+export const NoDataYet: Story = {
+ args: {
+ preset: "noDataYet"
+ }
+};
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/index.tsx
new file mode 100644
index 000000000..a06a31bb2
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/index.tsx
@@ -0,0 +1,72 @@
+import { useTheme, type DefaultTheme } from "styled-components";
+import { getThemeKind } from "../../../../common/App/styles";
+import { PulseIcon } from "../../../../common/icons/32px/PulseIcon";
+import { CardsColoredIcon } from "../../../../common/icons/CardsColoredIcon";
+import { EmptyState as CommonEmptyState } from "../../../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../../../common/v3/EmptyState/types";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset, theme: DefaultTheme) => {
+ const themeKind = getThemeKind(theme);
+
+ const content: Record = {
+ noFilteredData: {
+ icon: ,
+ title: "No data found",
+ message: "There are no insights for this criteria"
+ },
+ noSearchResults: {
+ icon: ,
+ title: "No results",
+ message: "Check spelling or try to search something else."
+ },
+ noDismissedData: {
+ icon: ,
+ title: "No dismissed issues"
+ },
+ noInsightsYet: {
+ icon: ,
+ title: "No insights yet"
+ },
+ noSpanDataYet: {
+ icon: ,
+ title: "No data yet",
+ message:
+ "No data received yet for this span, please trigger some actions using this code to see more insights."
+ },
+ analyticsSelectAsset: {
+ icon: ,
+ title: "Select an asset to view data",
+ message:
+ "The Analytics tab shows\nperformance data for each Digma-\ntracked asset. See all tracked assets\non the Assets page."
+ },
+ noDataYet: {
+ icon: ,
+ title: "No data yet",
+ message:
+ "Trigger actions that call this application to learn more about its runtime behavior"
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const theme = useTheme();
+
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset, theme) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/styles.ts
new file mode 100644
index 000000000..b0948d1d8
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/styles.ts
@@ -0,0 +1,6 @@
+import styled from "styled-components";
+import { Spinner as CommonSpinner } from "../../../../common/v3/Spinner";
+
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
+`;
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/types.ts
new file mode 100644
index 000000000..7ff5dd0b7
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/EmptyState/types.ts
@@ -0,0 +1,14 @@
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../../../common/v3/EmptyState/types";
+
+export type EmptyStatePreset =
+ | "noDismissedData"
+ | "noFilteredData"
+ | "noSearchResults"
+ | "noInsightsYet"
+ | "noDataYet"
+ | "noSpanDataYet"
+ | "analyticsSelectAsset";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
+}
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/InsightsPage.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/InsightsPage.stories.tsx
index 2935fad48..0482adc05 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/InsightsPage.stories.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/InsightsPage.stories.tsx
@@ -33,7 +33,6 @@ const scope: Scope = {
const props: InsightsPageProps = {
insights: [],
- isFilteringEnabled: false,
onJiraTicketCreate: () => {
return undefined;
},
@@ -108,25 +107,6 @@ export const NoInsightsAndNoAnalyticsAtSpan: Story = {
args: props
};
-export const NoInsightsWithAppliedFilters: Story = {
- decorators: [
- (Story) => (
-
-
-
- )
- ],
- args: {
- ...props,
- isFilteringEnabled: true
- }
-};
-
export const NoInsightsButAnalyticsExist: Story = {
decorators: [
(Story) => (
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
index 96ae3ea76..c168efbc9 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
import { actions as globalActions } from "../../../../actions";
import { usePersistence } from "../../../../hooks/usePersistence";
import { useConfigSelector } from "../../../../store/config/useConfigSelector";
+import { useInsightsSelector } from "../../../../store/insights/useInsightsSelector";
import { trackingEvents as globalEvents } from "../../../../trackingEvents";
import { isNumber } from "../../../../typeGuards/isNumber";
import { isUndefined } from "../../../../typeGuards/isUndefined";
@@ -11,11 +12,7 @@ import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserA
import { useHistory } from "../../../Main/useHistory";
import { TAB_IDS } from "../../../Navigation/Tabs/types";
import type { Scope } from "../../../common/App/types";
-import { EmptyState } from "../../../common/EmptyState";
-import { PulseIcon } from "../../../common/icons/32px/PulseIcon";
-import { CardsIcon } from "../../../common/icons/CardsIcon";
import { NewButton } from "../../../common/v3/NewButton";
-import { NewEmptyState } from "../../../common/v3/NewEmptyState";
import { actions } from "../../actions";
import { trackingEvents } from "../../tracking";
import {
@@ -46,6 +43,8 @@ import type {
InsightViewType,
Trace
} from "../../types";
+import { ViewMode } from "../types";
+import { EmptyState } from "./EmptyState";
import { EndpointBottleneckInsightCard } from "./insightCards/EndpointBottleneckInsightCard";
import { EndpointBreakdownInsightCard } from "./insightCards/EndpointBreakdownInsightCard";
import { EndpointChattyApiV2InsightCard } from "./insightCards/EndpointChattyApiV2InsightCard";
@@ -482,7 +481,9 @@ const renderInsightCard = (
};
const renderEmptyState = (
- isFilteringEnabled: boolean,
+ viewMode: ViewMode,
+ areAnyFiltersApplied: boolean,
+ search: string,
scope: Scope | null,
insightsViewType: InsightViewType | null,
goTo: (location: string) => void
@@ -509,18 +510,16 @@ const renderEmptyState = (
goTo(`/${TAB_IDS.ASSETS}`);
};
- if (isFilteringEnabled) {
- return (
-
- There are no insights for this criteria
-
- }
- />
- );
+ if (search.length > 0) {
+ return ;
+ }
+
+ if (areAnyFiltersApplied) {
+ return ;
+ }
+
+ if (viewMode === ViewMode.OnlyDismissed) {
+ return ;
}
if (
@@ -530,65 +529,35 @@ const renderEmptyState = (
) {
return (
+ preset={"noInsightsYet"}
+ message={
+ <>
Performing more actions that trigger this asset will increase the
chance of identifying insights. You can also check out the{" "}
analytics
{" "}
tab
-
+ >
}
/>
);
}
if (scope?.span) {
- return (
-
- No data received yet for this span, please trigger some actions
- using this code to see more insights.
-
- }
- />
- );
+ return ;
}
if (!scope?.span?.spanCodeObjectId && insightsViewType == "Analytics") {
return (
-
-
-
- The Analytics tab shows
-
-
- performance data for each Digma-
-
-
- tracked asset. See all tracked assets
-
-
- on the Assets page.
-
-
-
-
-
+
}
/>
);
@@ -596,18 +565,11 @@ const renderEmptyState = (
return (
-
- Trigger actions that call this application to learn more about its
- runtime behavior
-
-
- Not seeing your application data?
-
- >
+ preset={"noDataYet"}
+ customContent={
+
+ Not seeing your application data?
+
}
/>
);
@@ -622,10 +584,13 @@ export const InsightsPage = ({
onJiraTicketCreate,
onRefresh,
isMarkAsReadButtonEnabled,
- isFilteringEnabled,
insightsViewType
}: InsightsPageProps) => {
const { scope, environment } = useConfigSelector();
+ const { viewMode, search, filters, filteredInsightTypes } =
+ useInsightsSelector();
+ const areAnyFiltersApplied =
+ filters.length > 0 || filteredInsightTypes.length > 0 || search.length > 0;
const [isInsightJiraTicketHintShown, setIsInsightJiraTicketHintShown] =
usePersistence(
IS_INSIGHT_JIRA_TICKET_HINT_SHOWN_PERSISTENCE_KEY,
@@ -682,7 +647,14 @@ export const InsightsPage = ({
isAtSpan ? "full" : "compact"
);
})
- : renderEmptyState(isFilteringEnabled, scope, insightsViewType, goTo)}
+ : renderEmptyState(
+ viewMode,
+ areAnyFiltersApplied,
+ search,
+ scope,
+ insightsViewType,
+ goTo
+ )}
);
};
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/hooks/useDismissal.ts b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/hooks/useDismissal.ts
index 2a7a90142..b69014042 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/hooks/useDismissal.ts
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/hooks/useDismissal.ts
@@ -1,3 +1,4 @@
+import { useEffect, useState } from "react";
import { useAction } from "../../../../../../../../hooks/useAction";
import { actions } from "../../../../../../actions";
import type { DismissUndismissInsightPayload } from "../../../../../../types";
@@ -6,30 +7,57 @@ import type { DismissUndismissResponsePayload } from "../types";
const mapId = (response: DismissUndismissResponsePayload) => response.insightId;
export const useDismissal = (insightId: string) => {
- const { isOperationInProgress: isDismissInProgress, execute: dismiss } =
- useAction(
- actions.DISMISS,
- actions.SET_DISMISS_RESPONSE,
- {
- id: insightId,
- insightId
- },
- mapId
- );
+ const [data, setData] = useState<{
+ action: string;
+ payload: DismissUndismissResponsePayload;
+ } | null>(null);
+
+ const {
+ isOperationInProgress: isDismissInProgress,
+ execute: dismiss,
+ data: dismissData
+ } = useAction<
+ DismissUndismissInsightPayload,
+ DismissUndismissResponsePayload
+ >(
+ actions.DISMISS,
+ actions.SET_DISMISS_RESPONSE,
+ {
+ id: insightId,
+ insightId
+ },
+ mapId
+ );
+
+ const {
+ isOperationInProgress: isUndismissInProgress,
+ execute: undismiss,
+ data: undismissData
+ } = useAction<
+ DismissUndismissInsightPayload,
+ DismissUndismissResponsePayload
+ >(
+ actions.UNDISMISS,
+ actions.SET_UNDISMISS_RESPONSE,
+ {
+ id: insightId,
+ insightId
+ },
+ mapId
+ );
+
+ useEffect(() => {
+ setData(undismissData);
+ }, [undismissData]);
+
+ useEffect(() => {
+ setData(dismissData);
+ }, [dismissData]);
- const { isOperationInProgress: isUndismissInProgress, execute: undismiss } =
- useAction(
- actions.UNDISMISS,
- actions.SET_UNDISMISS_RESPONSE,
- {
- id: insightId,
- insightId
- },
- mapId
- );
return {
dismiss,
show: undismiss,
+ data,
isDismissalChangeInProgress: isDismissInProgress || isUndismissInProgress
};
};
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/index.tsx
index 495a33fca..c6cb96df6 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/index.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/insightCards/common/InsightCard/index.tsx
@@ -2,6 +2,8 @@ import { Fragment, useEffect, useRef, useState } from "react";
import { dispatcher } from "../../../../../../../dispatcher";
import { usePrevious } from "../../../../../../../hooks/usePrevious";
import { useConfigSelector } from "../../../../../../../store/config/useConfigSelector";
+import { useInsightsSelector } from "../../../../../../../store/insights/useInsightsSelector";
+import { useStore } from "../../../../../../../store/useStore";
import { isString } from "../../../../../../../typeGuards/isString";
import { sendUserActionTrackingEvent } from "../../../../../../../utils/actions/sendUserActionTrackingEvent";
import { getInsightTypeInfo } from "../../../../../../../utils/getInsightTypeInfo";
@@ -19,6 +21,7 @@ import { actions } from "../../../../../actions";
import { trackingEvents } from "../../../../../tracking";
import { isEndpointInsight, isSpanInsight } from "../../../../../typeGuards";
import { InsightStatus } from "../../../../../types";
+import { ViewMode } from "../../../../types";
import { IssueCompactCard } from "../IssueCompactCard";
import { ActionButton } from "./ActionButton";
import type { ActionButtonType } from "./ActionButton/types";
@@ -50,9 +53,15 @@ export const InsightCard = ({
viewMode,
mainMetric
}: InsightCardProps) => {
- const { isDismissalChangeInProgress, dismiss, show } = useDismissal(
- insight.id
- );
+ const { data } = useInsightsSelector();
+ const { setInsightsViewMode } = useStore.getState();
+ const {
+ isDismissalChangeInProgress,
+ dismiss,
+ show,
+ data: dismissalData
+ } = useDismissal(insight.id);
+ const previousDismissalData = usePrevious(dismissalData);
const { isMarkingAsReadInProgress, markAsRead } = useMarkingAsRead(
insight.id
);
@@ -104,6 +113,18 @@ export const InsightCard = ({
insight.type
]);
+ useEffect(() => {
+ if (
+ previousDismissalData !== dismissalData &&
+ dismissalData?.payload.status === "success" &&
+ dismissalData.action === actions.SET_UNDISMISS_RESPONSE &&
+ data?.dismissedCount === 1 &&
+ data.insights[0].id === dismissalData.payload.insightId
+ ) {
+ setInsightsViewMode(ViewMode.All);
+ }
+ }, [data, dismissalData, previousDismissalData, setInsightsViewMode]);
+
const handleRecheckButtonClick = () => {
if (onRecalculate) {
onRecalculate(insight.id);
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/styles.ts b/src/components/Insights/InsightsCatalog/InsightsPage/styles.ts
index 150c6d84a..af70008cc 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/styles.ts
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/styles.ts
@@ -1,5 +1,4 @@
import styled from "styled-components";
-import { footnoteRegularTypography } from "../../../common/App/typographies";
import { Link } from "../../../common/Link";
export const Container = styled.div`
@@ -11,39 +10,7 @@ export const Container = styled.div`
padding: 0 8px;
`;
-export const EmptyStateDescription = styled.span`
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#788ca9";
- case "dark":
- case "dark-jetbrains":
- return "#7c7c94";
- }
- }};
-`;
-
export const TroubleshootingLink = styled(Link)`
font-size: 14px;
text-decoration: underline;
`;
-
-export const HomeEmptyStateContainer = styled.div`
- display: flex;
- flex-direction: column;
- gap: 8px;
- align-items: center;
- padding-top: 4px;
-`;
-
-export const EmptyDescriptionContainer = styled.div`
- flex-direction: column;
- display: flex;
- text-align: center;
- ${footnoteRegularTypography}
- color: ${({ theme }) => theme.colors.v3.text.tertiary}
-`;
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/types.ts b/src/components/Insights/InsightsCatalog/InsightsPage/types.ts
index 2c94709ae..b7a2c26f0 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/types.ts
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/types.ts
@@ -6,7 +6,6 @@ import type {
export interface InsightsPageProps {
insights: GenericCodeObjectInsight[];
- isFilteringEnabled: boolean;
onJiraTicketCreate: (
insight: GenericCodeObjectInsight,
spanCodeObjectId?: string
diff --git a/src/components/Insights/InsightsCatalog/index.tsx b/src/components/Insights/InsightsCatalog/index.tsx
index 39be4fa71..bb2242be8 100644
--- a/src/components/Insights/InsightsCatalog/index.tsx
+++ b/src/components/Insights/InsightsCatalog/index.tsx
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import { useTheme } from "styled-components";
import { getFeatureFlagValue } from "../../../featureFlags";
-import { useDebounce } from "../../../hooks/useDebounce";
import { usePrevious } from "../../../hooks/usePrevious";
import { useConfigSelector } from "../../../store/config/useConfigSelector";
import { useInsightsSelector } from "../../../store/insights/useInsightsSelector";
@@ -72,8 +71,6 @@ export const InsightsCatalog = ({
backendInfo
} = useConfigSelector();
- const debouncedSearchInputValue = useDebounce(searchInputValue, 1000);
-
const insights = data?.insights ?? [];
const totalCount = data?.totalCount ?? 0;
const dismissedCount = data?.dismissedCount;
@@ -300,9 +297,6 @@ export const InsightsCatalog = ({
page={page}
insights={insights}
insightsViewType={insightViewType}
- isFilteringEnabled={
- debouncedSearchInputValue !== null && debouncedSearchInputValue !== ""
- }
onJiraTicketCreate={onJiraTicketCreate}
onRefresh={onRefresh}
isMarkAsReadButtonEnabled={isShowUnreadOnly(filters)}
diff --git a/src/components/Insights/index.tsx b/src/components/Insights/index.tsx
index 7c6ae5c15..388be10aa 100644
--- a/src/components/Insights/index.tsx
+++ b/src/components/Insights/index.tsx
@@ -1,27 +1,16 @@
import type { KeyboardEvent } from "react";
import { useCallback, useEffect, useState } from "react";
import { actions as globalActions } from "../../actions";
-import { SLACK_WORKSPACE_URL } from "../../constants";
import { usePersistence } from "../../hooks/usePersistence";
import { usePrevious } from "../../hooks/usePrevious";
import { useConfigSelector } from "../../store/config/useConfigSelector";
import { useInsightsSelector } from "../../store/insights/useInsightsSelector";
import { useStore } from "../../store/useStore";
-import { trackingEvents as globalTrackingEvents } from "../../trackingEvents";
import { isUndefined } from "../../typeGuards/isUndefined";
-import { openURLInDefaultBrowser } from "../../utils/actions/openURLInDefaultBrowser";
-import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent";
import { areBackendInfosEqual } from "../../utils/areBackendInfosEqual";
-import { CircleLoader } from "../common/CircleLoader";
-import { EmptyState } from "../common/EmptyState";
import { RegistrationDialog } from "../common/RegistrationDialog";
import type { RegistrationFormValues } from "../common/RegistrationDialog/types";
-import { CardsIcon } from "../common/icons/CardsIcon";
-import { DocumentWithMagnifierIcon } from "../common/icons/DocumentWithMagnifierIcon";
-import { LightBulbSmallCrossedIcon } from "../common/icons/LightBulbSmallCrossedIcon";
-import { LightBulbSmallIcon } from "../common/icons/LightBulbSmallIcon";
-import { OpenTelemetryLogoCrossedSmallIcon } from "../common/icons/OpenTelemetryLogoCrossedSmallIcon";
-import { SlackLogoIcon } from "../common/icons/SlackLogoIcon";
+import { EmptyState } from "./EmptyState";
import { InsightsCatalog } from "./InsightsCatalog";
import type { IssuesFilterQuery } from "./InsightsCatalog/FilterPanel/IssuesFilter/types";
import { EndpointBottleneckInsightTicket } from "./insightTickets/EndpointBottleneckInsightTicket";
@@ -173,46 +162,6 @@ const renderInsightTicket = (
return null;
};
-const NoDataYet = () => {
- const handleTroubleshootingLinkClick = () => {
- sendUserActionTrackingEvent(
- globalTrackingEvents.TROUBLESHOOTING_LINK_CLICKED,
- {
- origin: "insights"
- }
- );
-
- sendMessage(globalActions.OPEN_TROUBLESHOOTING_GUIDE);
- };
-
- return (
-
-
- Trigger actions that call this application to learn more about its
- runtime behavior
-
-
- Not seeing your application data?
-
- >
- }
- />
- );
-};
-
-const sendMessage = (action: string, data?: object) => {
- return window.sendMessageToDigma({
- action,
- payload: {
- ...data
- }
- });
-};
-
export const ISSUES_FILTERS_PERSISTENCE_KEY = "issuesFilters";
export const Insights = ({ insightViewType }: InsightsProps) => {
@@ -354,10 +303,6 @@ export const Insights = ({ insightViewType }: InsightsProps) => {
environmentId
]);
- const handleSlackLinkClick = () => {
- openURLInDefaultBrowser(SLACK_WORKSPACE_URL);
- };
-
const handleJiraTicketPopupOpen = useCallback(
(insight: GenericCodeObjectInsight, spanCodeObjectId?: string) => {
setInfoToOpenJiraTicket({ insight, spanCodeObjectId });
@@ -370,9 +315,12 @@ export const Insights = ({ insightViewType }: InsightsProps) => {
};
const handleRegistrationSubmit = (formData: RegistrationFormValues) => {
- sendMessage(globalActions.PERSONALIZE_REGISTER, {
- ...formData,
- scope: "insights view jira ticket info"
+ window.sendMessageToDigma({
+ action: globalActions.PERSONALIZE_REGISTER,
+ payload: {
+ ...formData,
+ scope: "insights view jira ticket info"
+ }
});
setIsRegistrationInProgress(true);
@@ -398,68 +346,24 @@ export const Insights = ({ insightViewType }: InsightsProps) => {
!storedInsightViewType ||
!areFiltersRehydrated;
if (isInitialLoading) {
- return } />;
+ return ;
}
if (!environments?.length) {
- return ;
+ return ;
}
switch (data?.insightsStatus) {
case InsightsStatus.STARTUP:
- return (
-
-
-
- Navigate to any code file in your workspace,
-
-
- or click a recent activity,
-
-
- to see runtime data and insights here.
-
-
-
-
- Join Our Slack Channel for Support
-
- >
- }
- />
- );
+ return ;
case InsightsStatus.NO_INSIGHTS:
- return (
-
- );
+ return ;
case InsightsStatus.INSIGHT_PENDING:
- return (
-
- );
+ return ;
case InsightsStatus.NO_SPANS_DATA:
- return ;
+ return ;
case InsightsStatus.NO_OBSERVABILITY:
- return (
-
-
- Add an annotation to observe this method and collect data
- about its runtime behavior
-
- >
- }
- />
- );
+ return ;
case InsightsStatus.DEFAULT:
default:
return (
diff --git a/src/components/Insights/styles.ts b/src/components/Insights/styles.ts
index 5d64ba8d6..9aa14c5c5 100644
--- a/src/components/Insights/styles.ts
+++ b/src/components/Insights/styles.ts
@@ -12,13 +12,6 @@ export const Container = styled.div`
position: relative;
`;
-export const StartupText = styled.span`
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
-`;
-
export const Description = styled.div`
display: flex;
gap: 8px;
@@ -69,33 +62,6 @@ export const Link = styled(CommonLink)`
}};
`;
-export const SlackLink = styled(Link)`
- display: flex;
- align-items: center;
- gap: 4px;
-`;
-
-export const EmptyStateDescription = styled.span`
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 4px;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#788ca9";
- case "dark":
- case "dark-jetbrains":
- return "#7c7c94";
- }
- }};
-`;
-
-export const TroubleshootingLink = styled(Link)`
- font-size: 14px;
- text-decoration: underline;
-`;
-
export const Overlay = styled.div`
position: fixed;
inset: 0;
diff --git a/src/components/Notifications/EmptyState/EmptyState.stories.tsx b/src/components/Notifications/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..f09829d84
--- /dev/null
+++ b/src/components/Notifications/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,40 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { EmptyState } from ".";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "Notifications/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const NoData: Story = {
+ args: {
+ preset: "noData"
+ }
+};
+
+export const NoUnreadData: Story = {
+ args: {
+ preset: "noUnreadData"
+ }
+};
+
+export const Loading: Story = {
+ args: {
+ preset: "loading"
+ }
+};
+
+export const Error: Story = {
+ args: {
+ preset: "error"
+ }
+};
diff --git a/src/components/Notifications/EmptyState/index.tsx b/src/components/Notifications/EmptyState/index.tsx
index 8790379f5..e2bd2273c 100644
--- a/src/components/Notifications/EmptyState/index.tsx
+++ b/src/components/Notifications/EmptyState/index.tsx
@@ -2,12 +2,60 @@ import { CrossedBellIcon } from "../../common/icons/CrossedBellIcon";
import * as s from "./styles";
import type { EmptyStateProps } from "./types";
-export const EmptyState = ({ title, content }: EmptyStateProps) => (
-
-
-
-
- {title}
- {content}
-
-);
+import { SLACK_WORKSPACE_URL } from "../../../constants";
+import { openURLInDefaultBrowser } from "../../../utils/actions/openURLInDefaultBrowser";
+import { SlackLogoIcon } from "../../common/icons/SlackLogoIcon";
+import { EmptyState as CommonEmptyState } from "../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+import type { EmptyStatePreset } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset) => {
+ const handleSlackLinkClick = () => {
+ openURLInDefaultBrowser(SLACK_WORKSPACE_URL);
+ };
+
+ const content: Record = {
+ noData: {
+ icon: ,
+ title: "No Notifications"
+ },
+ noUnreadData: {
+ icon: ,
+ title: "No Unread Notifications"
+ },
+ loading: {
+ icon: ,
+ title: "Fetching data"
+ },
+ error: {
+ icon: ,
+ title: "Unable To Get Notifications",
+ customContent: (
+
+
+ Having trouble? Please reach out in our Slack group
+
+ )
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Notifications/EmptyState/styles.ts b/src/components/Notifications/EmptyState/styles.ts
index 89d6265e9..79e8b4268 100644
--- a/src/components/Notifications/EmptyState/styles.ts
+++ b/src/components/Notifications/EmptyState/styles.ts
@@ -1,50 +1,13 @@
import styled from "styled-components";
+import { Link } from "../../common/Link";
+import { Spinner as CommonSpinner } from "../../common/v3/Spinner";
-export const Container = styled.div`
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- flex-grow: 1;
- gap: 8px;
-`;
-
-export const IconContainer = styled.div`
- width: 72px;
- height: 72px;
- border-radius: 50%;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#fff";
- case "dark":
- case "dark-jetbrains":
- return "#868a91";
- }
- }};
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#ebecf0";
- case "dark":
- case "dark-jetbrains":
- return "#393b40";
- }
- }};
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
`;
-export const Title = styled.div`
- text-transform: capitalize;
- font-size: 16px;
- font-weight: 500;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#494b57";
- case "dark":
- case "dark-jetbrains":
- return "#dfe1e5";
- }
- }};
+export const SlackLink = styled(Link)`
+ display: flex;
+ align-items: center;
+ gap: 4px;
`;
diff --git a/src/components/Notifications/EmptyState/types.ts b/src/components/Notifications/EmptyState/types.ts
index 4160d3470..463ba501c 100644
--- a/src/components/Notifications/EmptyState/types.ts
+++ b/src/components/Notifications/EmptyState/types.ts
@@ -1,6 +1,7 @@
-import type { ReactNode } from "react";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
-export interface EmptyStateProps {
- title: string;
- content?: ReactNode;
+export type EmptyStatePreset = "noData" | "noUnreadData" | "loading" | "error";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
}
diff --git a/src/components/Notifications/ErrorEmptyState/index.tsx b/src/components/Notifications/ErrorEmptyState/index.tsx
deleted file mode 100644
index a8dc2283c..000000000
--- a/src/components/Notifications/ErrorEmptyState/index.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { SLACK_WORKSPACE_URL } from "../../../constants";
-import { openURLInDefaultBrowser } from "../../../utils/actions/openURLInDefaultBrowser";
-import { SlackLogoIcon } from "../../common/icons/SlackLogoIcon";
-import { EmptyState } from "../EmptyState";
-import * as s from "./styles";
-
-export const ErrorEmptyState = () => {
- const handleSlackLinkClick = () => {
- openURLInDefaultBrowser(SLACK_WORKSPACE_URL);
- };
-
- return (
-
-
- Having trouble? Please reach out in our Slack group
-
- }
- />
- );
-};
diff --git a/src/components/Notifications/ErrorEmptyState/styles.ts b/src/components/Notifications/ErrorEmptyState/styles.ts
deleted file mode 100644
index b0d54a9e9..000000000
--- a/src/components/Notifications/ErrorEmptyState/styles.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import styled from "styled-components";
-import { Link } from "../../common/Link";
-
-export const SlackLink = styled(Link)`
- display: flex;
- align-items: center;
- gap: 4px;
-`;
diff --git a/src/components/Notifications/FullView/index.tsx b/src/components/Notifications/FullView/index.tsx
index d87b47bfc..2bcb403dc 100644
--- a/src/components/Notifications/FullView/index.tsx
+++ b/src/components/Notifications/FullView/index.tsx
@@ -1,10 +1,8 @@
import { useEffect } from "react";
-import { CircleLoader } from "../../common/CircleLoader";
import { Pagination } from "../../common/Pagination";
import { Toggle } from "../../common/Toggle";
import type { ToggleValue } from "../../common/Toggle/types";
import { EmptyState } from "../EmptyState";
-import { ErrorEmptyState } from "../ErrorEmptyState";
import { Header } from "../Header";
import { NotificationList } from "../NotificationList";
import type { CodeObjectData } from "../types";
@@ -53,19 +51,13 @@ export const FullView = ({
const renderEmptyState = () => {
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
return error ? (
-
+
) : (
-
+
);
};
diff --git a/src/components/Notifications/RecentView/index.tsx b/src/components/Notifications/RecentView/index.tsx
index ec4f5e40e..22071d22e 100644
--- a/src/components/Notifications/RecentView/index.tsx
+++ b/src/components/Notifications/RecentView/index.tsx
@@ -1,8 +1,6 @@
import { formatUnit } from "../../../utils/formatUnit";
-import { CircleLoader } from "../../common/CircleLoader";
import { Link } from "../../common/Link";
import { EmptyState } from "../EmptyState";
-import { ErrorEmptyState } from "../ErrorEmptyState";
import { Header } from "../Header";
import { NotificationList } from "../NotificationList";
import type { CodeObjectData } from "../types";
@@ -33,17 +31,13 @@ export const RecentView = ({
const renderEmptyState = () => {
if (isLoading) {
- return (
-
-
-
- );
+ return ;
}
return error ? (
-
+
) : (
-
+
);
};
diff --git a/src/components/Tests/EmptyState/EmptyState.stories.tsx b/src/components/Tests/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..a79ee3ddd
--- /dev/null
+++ b/src/components/Tests/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,40 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { EmptyState } from ".";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "Tests/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const NoData: Story = {
+ args: {
+ preset: "noData"
+ }
+};
+
+export const NoFilteredData: Story = {
+ args: {
+ preset: "noFilteredData"
+ }
+};
+
+export const SelectAsset: Story = {
+ args: {
+ preset: "selectAsset"
+ }
+};
+
+export const Loading: Story = {
+ args: {
+ preset: "loading"
+ }
+};
diff --git a/src/components/Tests/EmptyState/index.tsx b/src/components/Tests/EmptyState/index.tsx
new file mode 100644
index 000000000..d237e6e33
--- /dev/null
+++ b/src/components/Tests/EmptyState/index.tsx
@@ -0,0 +1,50 @@
+import { RefreshIcon } from "../../common/icons/16px/RefreshIcon";
+import { EmptyState as CommonEmptyState } from "../../common/v3/EmptyState";
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+import * as s from "./styles";
+import type { EmptyStatePreset, EmptyStateProps } from "./types";
+
+const getPresetContent = (preset: EmptyStatePreset) => {
+ const content: Record = {
+ noData: {
+ title: "Run tests with Digma",
+ message:
+ "Run your test with Digma enabled to see related tests and insights"
+ },
+ noFilteredData: {
+ title: "No results",
+ message:
+ "It seems there are no tests matching your selected filters at the moment"
+ },
+ selectAsset: {
+ icon: ,
+ title: "Select an asset to run tests",
+ message:
+ "The Tests tab shows details for\ntests for each Digma-tracked\nasset. See all tracked assets on the\nAssets page."
+ },
+ loading: {
+ icon: ,
+ title: "Fetching data"
+ }
+ };
+
+ return content[preset];
+};
+
+export const EmptyState = ({
+ preset,
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => {
+ const props: EmptyStateProps = {
+ ...(preset ? getPresetContent(preset) : {}),
+ ...(icon ? { icon } : {}),
+ ...(title ? { title } : {}),
+ ...(message ? { message } : {}),
+ ...(customContent ? { customContent } : {})
+ };
+
+ return ;
+};
diff --git a/src/components/Tests/EmptyState/styles.ts b/src/components/Tests/EmptyState/styles.ts
new file mode 100644
index 000000000..af9498e4b
--- /dev/null
+++ b/src/components/Tests/EmptyState/styles.ts
@@ -0,0 +1,6 @@
+import styled from "styled-components";
+import { Spinner as CommonSpinner } from "../../common/v3/Spinner";
+
+export const Spinner = styled(CommonSpinner)`
+ color: ${({ theme }) => theme.colors.v3.surface.gray};
+`;
diff --git a/src/components/Tests/EmptyState/types.ts b/src/components/Tests/EmptyState/types.ts
new file mode 100644
index 000000000..2de093121
--- /dev/null
+++ b/src/components/Tests/EmptyState/types.ts
@@ -0,0 +1,11 @@
+import type { EmptyStateProps as CommonEmptyStateProps } from "../../common/v3/EmptyState/types";
+
+export type EmptyStatePreset =
+ | "noData"
+ | "noFilteredData"
+ | "selectAsset"
+ | "loading";
+
+export interface EmptyStateProps extends CommonEmptyStateProps {
+ preset?: EmptyStatePreset;
+}
diff --git a/src/components/Tests/index.tsx b/src/components/Tests/index.tsx
index 95e50f82d..f1fb37987 100644
--- a/src/components/Tests/index.tsx
+++ b/src/components/Tests/index.tsx
@@ -11,13 +11,11 @@ import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionT
import { useHistory } from "../Main/useHistory";
import { TAB_IDS } from "../Navigation/Tabs/types";
import type { MenuItem } from "../common/FilterMenu/types";
-import { NewCircleLoader } from "../common/NewCircleLoader";
import { Pagination } from "../common/Pagination";
import { RegistrationDialog } from "../common/RegistrationDialog";
import type { RegistrationFormValues } from "../common/RegistrationDialog/types";
-import { RefreshIcon } from "../common/icons/16px/RefreshIcon";
import { NewButton } from "../common/v3/NewButton";
-import { NewEmptyState } from "../common/v3/NewEmptyState";
+import { EmptyState } from "./EmptyState";
import { EnvironmentFilter } from "./EnvironmentFilter";
import { TestCard } from "./TestCard";
import { TestTicket } from "./TestTicket";
@@ -89,6 +87,7 @@ export const Tests = () => {
const scopeSpan = scope?.span ?? null;
const previousScopeSpan = usePrevious(scopeSpan);
const { goTo } = useHistory();
+ const areAnyFiltersApplied = selectedEnvironments.length > 0;
const environmentMenuItems: MenuItem[] = (environments ?? []).map(
(environment) => ({
@@ -250,6 +249,10 @@ export const Tests = () => {
}
};
+ const handleResetFiltersButtonClick = () => {
+ setSelectedEnvironments([]);
+ };
+
const handlePageChange = (page: number) => {
window.sendMessageToDigma({
action: actions.GET_SPAN_LATEST_DATA,
@@ -276,26 +279,29 @@ export const Tests = () => {
const renderContent = () => {
if (isInitialLoading) {
- return (
-
-
-
- );
+ return ;
}
if (data?.error) {
- return {data.error.message};
+ return ;
}
if (data?.data?.entries.length === 0) {
- return (
-
- Run tests with Digma
-
- Run your test with Digma enabled to see related tests and insights
-
-
- );
+ if (areAnyFiltersApplied) {
+ return (
+
+ }
+ />
+ );
+ }
+
+ return ;
}
return (
@@ -353,28 +359,16 @@ export const Tests = () => {
)}
>
) : (
-
-
-
- The Errors tab shows details for
- exceptions for each Digma-tracked
- asset. See all tracked assets on the
- Assets page.
-
-
-
- >
- }
- />
-
+
+ }
+ />
)}
);
diff --git a/src/components/Tests/styles.ts b/src/components/Tests/styles.ts
index 96b575dac..d04e2605f 100644
--- a/src/components/Tests/styles.ts
+++ b/src/components/Tests/styles.ts
@@ -1,6 +1,5 @@
import styled from "styled-components";
import { LAYERS } from "../common/App/styles";
-import { footnoteRegularTypography } from "../common/App/typographies";
export const Container = styled.div`
height: 100%;
@@ -9,18 +8,6 @@ export const Container = styled.div`
overflow: hidden;
`;
-export const NoDataContainer = styled.div`
- flex-grow: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 8px;
- text-align: center;
- color: ${({ theme }) => theme.colors.text.subtext};
- font-size: 14px;
-`;
-
export const EnvironmentFilterContainer = styled.div`
display: flex;
flex-direction: column;
@@ -46,18 +33,6 @@ export const TestsList = styled.div`
gap: 12px;
`;
-export const EmptyStateTextContainer = styled.div`
- ${footnoteRegularTypography}
-
- display: flex;
- flex-direction: column;
- text-align: center;
- gap: 4px;
- padding-top: 4px;
- padding-bottom: 4px;
- color: ${({ theme }) => theme.colors.v3.text.tertiary};
-`;
-
export const PaginationContainer = styled.div`
display: flex;
justify-content: space-between;
diff --git a/src/components/common/EmptyState/index.tsx b/src/components/common/EmptyState/index.tsx
deleted file mode 100644
index bbd1fe467..000000000
--- a/src/components/common/EmptyState/index.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useTheme } from "styled-components";
-import { getThemeKind } from "../../common/App/styles";
-import * as s from "./styles";
-import type { EmptyStateProps } from "./types";
-
-export const EmptyState = ({ icon: Icon, title, content }: EmptyStateProps) => {
- const theme = useTheme();
- const themeKind = getThemeKind(theme);
-
- return (
-
- {Icon && (
-
-
-
- )}
- {title}
- {content}
-
- );
-};
diff --git a/src/components/common/EmptyState/styles.ts b/src/components/common/EmptyState/styles.ts
deleted file mode 100644
index 7fea143c9..000000000
--- a/src/components/common/EmptyState/styles.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import styled from "styled-components";
-
-export const Container = styled.div`
- display: flex;
- align-items: center;
- justify-content: center;
- flex-direction: column;
- padding: 0 40px;
- gap: 4px;
- flex-grow: 1;
-`;
-
-export const IconContainer = styled.div`
- width: 72px;
- height: 72px;
- border-radius: 50%;
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#d0d6eb";
- case "dark":
- case "dark-jetbrains":
- return "#323334";
- }
- }};
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#fbfdff";
- case "dark":
- case "dark-jetbrains":
- return "#9b9b9b";
- }
- }};
-`;
-
-export const Title = styled.div`
- margin-top: 4px;
- text-transform: capitalize;
- font-size: 16px;
- font-weight: 500;
- text-align: center;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#4d668a";
- case "dark":
- case "dark-jetbrains":
- return "#dadada";
- }
- }};
-`;
diff --git a/src/components/common/EmptyState/types.ts b/src/components/common/EmptyState/types.ts
deleted file mode 100644
index 8d4d5a30a..000000000
--- a/src/components/common/EmptyState/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { ComponentType, ReactNode } from "react";
-import type { ThemeableIconProps } from "../../common/icons/types";
-
-export interface EmptyStateProps {
- icon?: ComponentType;
- title?: string;
- content?: ReactNode;
-}
diff --git a/src/components/common/icons/LightBulbSmallIcon.tsx b/src/components/common/icons/LightBulbSmallIcon.tsx
index 90d3dcb42..acdcaad4e 100644
--- a/src/components/common/icons/LightBulbSmallIcon.tsx
+++ b/src/components/common/icons/LightBulbSmallIcon.tsx
@@ -1,9 +1,9 @@
import React from "react";
import { useIconProps } from "./hooks";
-import type { ThemeableIconProps } from "./types";
+import type { IconProps } from "./types";
-const LightBulbSmallIconComponent = (props: ThemeableIconProps) => {
- const { size } = useIconProps(props);
+const LightBulbSmallIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
return (
diff --git a/src/components/common/v3/EmptyState/EmptyState.stories.tsx b/src/components/common/v3/EmptyState/EmptyState.stories.tsx
new file mode 100644
index 000000000..f3adad89c
--- /dev/null
+++ b/src/components/common/v3/EmptyState/EmptyState.stories.tsx
@@ -0,0 +1,25 @@
+import type { Meta, StoryObj } from "@storybook/react";
+import { EmptyState } from ".";
+import { CrossCircleIcon } from "../../icons/CrossCircleIcon";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "common/v3/EmptyState",
+ component: EmptyState,
+ parameters: {
+ // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
+ layout: "fullscreen"
+ }
+};
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Default: Story = {
+ args: {
+ icon: ,
+ title: "No data",
+ message: "No data available"
+ }
+};
diff --git a/src/components/common/v3/EmptyState/index.tsx b/src/components/common/v3/EmptyState/index.tsx
new file mode 100644
index 000000000..06d979b7f
--- /dev/null
+++ b/src/components/common/v3/EmptyState/index.tsx
@@ -0,0 +1,22 @@
+import * as s from "./styles";
+import type { EmptyStateProps } from "./types";
+
+export const EmptyState = ({
+ icon,
+ title,
+ message,
+ customContent
+}: EmptyStateProps) => (
+
+
+ {icon && {icon}}
+ {(title ?? message) && (
+
+ {title && {title}}
+ {message && {message}}
+
+ )}
+ {customContent}
+
+
+);
diff --git a/src/components/common/v3/NewEmptyState/styles.ts b/src/components/common/v3/EmptyState/styles.ts
similarity index 56%
rename from src/components/common/v3/NewEmptyState/styles.ts
rename to src/components/common/v3/EmptyState/styles.ts
index 8dc295729..42f28246c 100644
--- a/src/components/common/v3/NewEmptyState/styles.ts
+++ b/src/components/common/v3/EmptyState/styles.ts
@@ -1,30 +1,46 @@
import styled from "styled-components";
-import { bodySemiboldTypography } from "../../App/typographies";
+import {
+ bodySemiboldTypography,
+ footnoteRegularTypography
+} from "../../../common/App/typographies";
export const Container = styled.div`
display: flex;
align-items: center;
justify-content: center;
+ flex-grow: 1;
+`;
+
+export const ContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+`;
+
+export const TextContainer = styled.div`
+ display: flex;
flex-direction: column;
- padding: 0 40px;
+ text-align: center;
gap: 4px;
- flex-grow: 1;
+ white-space: pre-line;
+ ${footnoteRegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.tertiary};
+ max-width: 210px;
`;
export const IconContainer = styled.div`
- width: 50px;
- height: 50px;
display: flex;
align-items: center;
justify-content: center;
+ width: 50px;
+ height: 50px;
border-radius: 50%;
- background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
color: ${({ theme }) => theme.colors.v3.surface.gray};
+ background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
`;
-export const Title = styled.div`
+export const Title = styled.span`
${bodySemiboldTypography}
- text-align: center;
color: ${({ theme }) => theme.colors.v3.text.primary};
- margin-top: 4px;
`;
diff --git a/src/components/common/v3/EmptyState/types.ts b/src/components/common/v3/EmptyState/types.ts
new file mode 100644
index 000000000..d3749661d
--- /dev/null
+++ b/src/components/common/v3/EmptyState/types.ts
@@ -0,0 +1,8 @@
+import type { ReactNode } from "react";
+
+export interface EmptyStateProps {
+ icon?: ReactNode;
+ title?: string;
+ message?: ReactNode;
+ customContent?: ReactNode;
+}
diff --git a/src/components/common/v3/NewEmptyState/index.tsx b/src/components/common/v3/NewEmptyState/index.tsx
deleted file mode 100644
index a4da4fcb6..000000000
--- a/src/components/common/v3/NewEmptyState/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as s from "./styles";
-import type { EmptyStateProps } from "./types";
-
-export const NewEmptyState = ({
- icon: Icon,
- title,
- content
-}: EmptyStateProps) => {
- return (
-
- {Icon && (
-
-
-
- )}
- {title}
- {content}
-
- );
-};
diff --git a/src/components/common/v3/NewEmptyState/types.ts b/src/components/common/v3/NewEmptyState/types.ts
deleted file mode 100644
index fb717673d..000000000
--- a/src/components/common/v3/NewEmptyState/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { ComponentType, ReactNode } from "react";
-import type { IconProps } from "../../../common/icons/types";
-
-export interface EmptyStateProps {
- icon?: ComponentType;
- title?: string;
- content?: ReactNode;
-}
diff --git a/src/components/common/v3/Toggle/styles.ts b/src/components/common/v3/Toggle/styles.ts
index 1ad7f8220..159dc869c 100644
--- a/src/components/common/v3/Toggle/styles.ts
+++ b/src/components/common/v3/Toggle/styles.ts
@@ -21,7 +21,7 @@ export const OptionButton = styled.button`
outline: none;
cursor: pointer;
user-select: none;
- border-radius: ${({ $size }) => ($size === "small" ? "2px" : " 4px")};
+ border-radius: ${({ $size }) => ($size === "small" ? "2px" : "4px")};
padding: ${({ $size }) => ($size === "small" ? "2px" : "2px 4px")};
color: ${({ theme, $selected }) =>
$selected ? theme.colors.v3.text.white : theme.colors.v3.text.primary};