diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx
index 3e15c86e0..ee7b03871 100644
--- a/src/components/Assets/AssetList/index.tsx
+++ b/src/components/Assets/AssetList/index.tsx
@@ -4,6 +4,7 @@ import { DigmaMessageError } from "../../../api/types";
import { dispatcher } from "../../../dispatcher";
import { usePrevious } from "../../../hooks/usePrevious";
import { useConfigSelector } from "../../../store/config/useConfigSelector";
+import { useStore } from "../../../store/useStore";
import { isEnvironment } from "../../../typeGuards/isEnvironment";
import { changeScope } from "../../../utils/actions/changeScope";
import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
@@ -147,6 +148,7 @@ export const AssetList = ({
const previousEnvironment = usePrevious(environment);
const previousViewScope = usePrevious(scopeViewOptions);
const isServicesFilterEnabled = !scope?.span?.spanCodeObjectId;
+ const { setShowAssetsHeaderToolBox } = useStore.getState();
const refreshData = useCallback(() => {
getData(
@@ -204,6 +206,7 @@ export const AssetList = ({
};
dispatcher.addActionListener(actions.SET_DATA, handleAssetsData);
+ setShowAssetsHeaderToolBox(true);
return () => {
dispatcher.removeActionListener(actions.SET_DATA, handleAssetsData);
diff --git a/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx b/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx
index 57266a477..595df1d53 100644
--- a/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx
+++ b/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx
@@ -82,3 +82,37 @@ export const Empty: Story = {
}, 0);
}
};
+
+export const EmptyWithParents: Story = {
+ args: {
+ searchQuery: "",
+ setRefresher: () => {
+ return undefined;
+ }
+ },
+ play: () => {
+ window.setTimeout(() => {
+ window.postMessage({
+ type: "digma",
+ action: actions.SET_CATEGORIES_DATA,
+ payload: {
+ assetCategories: [],
+ parents: [
+ {
+ name: "http test",
+ displayName: "http get one",
+ instrumentationLibrary: "common",
+ spanCodeObjectId: "some span"
+ },
+ {
+ name: "http test 2",
+ displayName: "http get two",
+ instrumentationLibrary: "common",
+ spanCodeObjectId: "some span 2"
+ }
+ ]
+ }
+ });
+ }, 0);
+ }
+};
diff --git a/src/components/Assets/AssetTypeList/index.tsx b/src/components/Assets/AssetTypeList/index.tsx
index 902153e21..20e1d8b00 100644
--- a/src/components/Assets/AssetTypeList/index.tsx
+++ b/src/components/Assets/AssetTypeList/index.tsx
@@ -3,12 +3,18 @@ import { DigmaMessageError } from "../../../api/types";
import { dispatcher } from "../../../dispatcher";
import { usePrevious } from "../../../hooks/usePrevious";
import { useConfigSelector } from "../../../store/config/useConfigSelector";
+import { useStore } from "../../../store/useStore";
import { isEnvironment } from "../../../typeGuards/isEnvironment";
import { isNull } from "../../../typeGuards/isNull";
import { isString } from "../../../typeGuards/isString";
+import { changeScope } from "../../../utils/actions/changeScope";
+import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent";
+import { SCOPE_CHANGE_EVENTS } from "../../Main/types";
+import { ChildIcon } from "../../common/icons/30px/ChildIcon";
import { AssetFilterQuery } from "../AssetsFilter/types";
import { NoDataMessage } from "../NoDataMessage";
import { actions } from "../actions";
+import { trackingEvents } from "../tracking";
import { checkIfAnyFiltersApplied, getAssetTypeInfo } from "../utils";
import { AssetTypeListItem } from "./AssetTypeListItem";
import * as s from "./styles";
@@ -75,6 +81,8 @@ export const AssetTypeList = ({
const previousSearchQuery = usePrevious(searchQuery);
const previousViewScope = usePrevious(scopeViewOptions);
const isServicesFilterEnabled = !scope?.span?.spanCodeObjectId;
+ const { setShowAssetsHeaderToolBox } = useStore.getState();
+ const [showNoDataWithParents, setShowNoDataWithParents] = useState(false);
const isInitialLoading = !data;
@@ -137,8 +145,15 @@ export const AssetTypeList = ({
useEffect(() => {
if (data && previousData !== data) {
onAssetCountChange(getAssetCount(data));
+ const showNoDataWithParents = Boolean(
+ data?.parents &&
+ data.parents.length > 0 &&
+ data?.assetCategories.every((x) => x.count === 0)
+ );
+ setShowAssetsHeaderToolBox(!showNoDataWithParents);
+ setShowNoDataWithParents(showNoDataWithParents);
}
- }, [previousData, data, onAssetCountChange]);
+ }, [previousData, data, onAssetCountChange, setShowAssetsHeaderToolBox]);
useEffect(() => {
if (
@@ -172,6 +187,16 @@ export const AssetTypeList = ({
onAssetTypeSelect(assetTypeId);
};
+ const handleAssetLinkClick = (spanCodeObjectId: string) => {
+ sendUserActionTrackingEvent(trackingEvents.ALL_ASSETS_LINK_CLICKED);
+ changeScope({
+ span: { spanCodeObjectId },
+ context: {
+ event: SCOPE_CHANGE_EVENTS.ASSETS_EMPTY_CATEGORY_PARENT_LINK_CLICKED
+ }
+ });
+ };
+
if (isInitialLoading) {
return ;
}
@@ -181,11 +206,42 @@ export const AssetTypeList = ({
return ;
}
- if (scope !== null) {
- return ;
+ if (!scope) {
+ 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}
+
+ ))}
+ >
+ }
+ />
+
+ );
}
- 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 2f2e0ef59..5d6cca4a1 100644
--- a/src/components/Assets/AssetTypeList/styles.ts
+++ b/src/components/Assets/AssetTypeList/styles.ts
@@ -1,4 +1,10 @@
import styled from "styled-components";
+import {
+ footnoteRegularTypography,
+ subscriptRegularTypography
+} from "../../common/App/typographies";
+import { Link } from "../../common/v3/Link";
+import { NewEmptyState } from "../../common/v3/NewEmptyState";
export const List = styled.ul`
display: flex;
@@ -7,3 +13,34 @@ export const List = styled.ul`
padding: 0 8px 8px;
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/AssetTypeList/types.ts b/src/components/Assets/AssetTypeList/types.ts
index cee38169c..8f81139b5 100644
--- a/src/components/Assets/AssetTypeList/types.ts
+++ b/src/components/Assets/AssetTypeList/types.ts
@@ -1,4 +1,5 @@
import { MemoExoticComponent } from "react";
+import { SpanInfo } from "../../../types";
import { IconProps } from "../../common/icons/types";
import { AssetFilterQuery } from "../AssetsFilter/types";
import { AssetScopeOption } from "../AssetsViewScopeConfiguration/types";
@@ -17,6 +18,7 @@ export interface AssetCategoriesData {
name: string;
count: number;
}[];
+ parents?: SpanInfo[];
}
export interface AssetCategoryData {
diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx
index 21f715ac3..311299aba 100644
--- a/src/components/Assets/index.tsx
+++ b/src/components/Assets/index.tsx
@@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
import { getFeatureFlagValue } from "../../featureFlags";
import { useDebounce } from "../../hooks/useDebounce";
import { usePrevious } from "../../hooks/usePrevious";
+import { useAssetsSelector } from "../../store/assetsSlice/useAssetsSelector";
import { useConfigSelector } from "../../store/config/useConfigSelector";
import { FeatureFlag } from "../../types";
import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent";
@@ -45,6 +46,7 @@ export const Assets = () => {
backendInfo,
FeatureFlag.ARE_EXTENDED_ASSETS_FILTERS_ENABLED
);
+ const { showAssetsHeaderToolBox } = useAssetsSelector();
useEffect(() => {
if (!scope?.span) {
@@ -131,7 +133,7 @@ export const Assets = () => {
return ;
}
- if (!selectedFilters) {
+ if (!selectedFilters && showAssetsHeaderToolBox) {
return ;
}
@@ -173,33 +175,40 @@ export const Assets = () => {
/>
)}
-
-
-
-
-
-
-
- {scope?.span && (
- Assets filtered to current scope
+ {showAssetsHeaderToolBox && (
+ <>
+
+
+
+
+
+
+
+ {scope?.span && (
+ Assets filtered to current scope
+ )}
+ >
)}
+
{renderContent()}
);
diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
index 53861352c..e375a1409 100644
--- a/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
+++ b/src/components/Insights/InsightsCatalog/InsightsPage/index.tsx
@@ -562,7 +562,7 @@ const renderEmptyState = (
);
}
- if (!scope && insightsViewType == "Analytics") {
+ if (!scope?.span?.spanCodeObjectId && insightsViewType == "Analytics") {
return (
{
}
goTo(`/${TAB_IDS.ISSUES}`, { state });
break;
+ case SCOPE_CHANGE_EVENTS.ASSETS_EMPTY_CATEGORY_PARENT_LINK_CLICKED as string:
+ goTo(`/${TAB_IDS.ASSETS}`, { state });
+ break;
case SCOPE_CHANGE_EVENTS.IDE_CODE_LENS_CLICKED as string: {
const url = getURLToNavigateOnCodeLensClick(scope);
if (url) {
@@ -212,6 +215,7 @@ export const Main = () => {
break;
}
}
+
// falls through
case SCOPE_CHANGE_EVENTS.DASHBOARD_SLOW_QUERIES_WIDGET_ITEM_LINK_CLICKED as string:
case SCOPE_CHANGE_EVENTS.DASHBOARD_CLIENT_SPANS_PERFORMANCE_IMPACT_WIDGET_ITEM_LINK_CLICKED as string:
diff --git a/src/components/Main/types.ts b/src/components/Main/types.ts
index d62946312..41017adaf 100644
--- a/src/components/Main/types.ts
+++ b/src/components/Main/types.ts
@@ -55,7 +55,8 @@ export enum SCOPE_CHANGE_EVENTS {
NOTIFICATIONS_NOTIFICATION_CARD_ASSET_LINK_CLICKED = "NOTIFICATIONS/NOTIFICATION_CARD_ASSET_LINK_CLICKED",
RECENT_ACTIVITY_SPAN_LINK_CLICKED = "RECENT_ACTIVITY_SPAN_LINK_CLICKED",
IDE_CODE_LENS_CLICKED = "IDE/CODE_LENS_CLICKED",
- IDE_NOTIFICATION_LINK_CLICKED = "IDE/NOTIFICATION_LINK_CLICKED"
+ IDE_NOTIFICATION_LINK_CLICKED = "IDE/NOTIFICATION_LINK_CLICKED",
+ ASSETS_EMPTY_CATEGORY_PARENT_LINK_CLICKED = "ASSETS/EMPTY_CATEGORY_PARENT_LINK_CLICKED"
}
export interface ReactRouterLocationState {
diff --git a/src/components/common/icons/30px/ChildIcon.tsx b/src/components/common/icons/30px/ChildIcon.tsx
new file mode 100644
index 000000000..0f4c0348e
--- /dev/null
+++ b/src/components/common/icons/30px/ChildIcon.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { useIconProps } from "../hooks";
+import { IconProps } from "../types";
+
+const ChildIconComponent = (props: IconProps) => {
+ const { size } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const ChildIcon = React.memo(ChildIconComponent);
diff --git a/src/store/assetsSlice/assetsSlice.ts b/src/store/assetsSlice/assetsSlice.ts
new file mode 100644
index 000000000..6ad581221
--- /dev/null
+++ b/src/store/assetsSlice/assetsSlice.ts
@@ -0,0 +1,23 @@
+import { createSlice } from "zustand-slices";
+
+interface AssetsState {
+ showAssetsHeaderToolBox: boolean;
+}
+
+const initialState: AssetsState = {
+ showAssetsHeaderToolBox: true
+};
+
+const set = (update: Partial) => (state: AssetsState) => ({
+ ...state,
+ ...update
+});
+
+export const assetsSlice = createSlice({
+ name: "assets",
+ value: initialState,
+ actions: {
+ setShowAssetsHeaderToolBox: (showAssetsHeaderToolBox: boolean) =>
+ set({ showAssetsHeaderToolBox })
+ }
+});
diff --git a/src/store/assetsSlice/useAssetsSelector.ts b/src/store/assetsSlice/useAssetsSelector.ts
new file mode 100644
index 000000000..759e16d26
--- /dev/null
+++ b/src/store/assetsSlice/useAssetsSelector.ts
@@ -0,0 +1,3 @@
+import { useStore } from "../useStore";
+
+export const useAssetsSelector = () => useStore((state) => state.assets);
diff --git a/src/store/useStore.ts b/src/store/useStore.ts
index 827bee2ba..01c6a2acb 100644
--- a/src/store/useStore.ts
+++ b/src/store/useStore.ts
@@ -1,12 +1,13 @@
import { create } from "zustand";
import { withSlices } from "zustand-slices";
import { Scope } from "../components/common/App/types";
+import { assetsSlice } from "./assetsSlice/assetsSlice";
import { configSlice } from "./config/configSlice";
import { insightsSlice } from "./insights/insightsSlice";
import { withMutableActions } from "./withMutableActions";
export const useStore = create(
- withMutableActions(withSlices(configSlice, insightsSlice), {
+ withMutableActions(withSlices(configSlice, insightsSlice, assetsSlice), {
setScope: (scope: Scope) => (_, set) => {
set((state) =>
state.config.scope?.span?.spanCodeObjectId !==