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 ( { viewBox="0 0 72 72" > 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};