diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx
index ba3b33a0c..3451cec11 100644
--- a/src/components/Assets/AssetList/index.tsx
+++ b/src/components/Assets/AssetList/index.tsx
@@ -301,6 +301,12 @@ export const AssetList = (props: AssetListProps) => {
};
}, []);
+ useEffect(() => {
+ if (data && previousData?.filteredCount !== data?.filteredCount) {
+ props.onAssetCountChange(data.filteredCount);
+ }
+ }, [previousData, data, props.onAssetCountChange]);
+
useEffect(() => {
props.setRefresher(refreshData);
}, [refreshData, props.setRefresher]);
diff --git a/src/components/Assets/AssetList/styles.ts b/src/components/Assets/AssetList/styles.ts
index c35b594e5..c1a1f1b62 100644
--- a/src/components/Assets/AssetList/styles.ts
+++ b/src/components/Assets/AssetList/styles.ts
@@ -35,15 +35,6 @@ export const Header = styled.div`
font-weight: 400;
font-size: 14px;
padding: 8px 12px 8px 8px;
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#f1f5fa";
- case "dark":
- case "dark-jetbrains":
- return "#383838";
- }
- }};
color: ${({ theme }) => {
switch (theme.mode) {
case "light":
diff --git a/src/components/Assets/AssetList/types.ts b/src/components/Assets/AssetList/types.ts
index b8e34fa21..d5a5ec4bc 100644
--- a/src/components/Assets/AssetList/types.ts
+++ b/src/components/Assets/AssetList/types.ts
@@ -11,6 +11,7 @@ export interface AssetListProps {
searchQuery: string;
scopeViewOptions: AssetScopeOption | null;
setRefresher: (refresher: () => void) => void;
+ onAssetCountChange: (count: number) => void;
}
export enum SORTING_CRITERION {
diff --git a/src/components/Assets/AssetTypeList/AssetTypeListItem/index.tsx b/src/components/Assets/AssetTypeList/AssetTypeListItem/index.tsx
index 2b7c565f6..2cec1f0e1 100644
--- a/src/components/Assets/AssetTypeList/AssetTypeListItem/index.tsx
+++ b/src/components/Assets/AssetTypeList/AssetTypeListItem/index.tsx
@@ -8,7 +8,7 @@ export const AssetTypeListItem = (props: AssetTypeListItemProps) => {
return (
- {props.icon && }
+ {props.icon && }
{props.label || props.id}
{props.entryCount}
diff --git a/src/components/Assets/AssetTypeList/AssetTypeListItem/styles.ts b/src/components/Assets/AssetTypeList/AssetTypeListItem/styles.ts
index 7f8b1f498..4c8382dd6 100644
--- a/src/components/Assets/AssetTypeList/AssetTypeListItem/styles.ts
+++ b/src/components/Assets/AssetTypeList/AssetTypeListItem/styles.ts
@@ -10,26 +10,11 @@ export const ListItem = styled.li`
letter-spacing: -0.1px;
user-select: none;
border-radius: 4px;
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#f1f5fa";
- case "dark":
- case "dark-jetbrains":
- return "#383838";
- }
- }};
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.tertiary};
+ background: ${({ theme }) => theme.colors.v3.surface.secondary};
+ color: ${({ theme }) => theme.colors.v3.text.primary};
`;
export const EntryCount = styled.span`
margin-left: auto;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#828797";
- case "dark":
- case "dark-jetbrains":
- return "#9b9b9b";
- }
- }};
`;
diff --git a/src/components/Assets/AssetTypeList/index.tsx b/src/components/Assets/AssetTypeList/index.tsx
index f5179062a..8bc3b2095 100644
--- a/src/components/Assets/AssetTypeList/index.tsx
+++ b/src/components/Assets/AssetTypeList/index.tsx
@@ -63,6 +63,9 @@ const getData = (
});
};
+const getAssetCount = (assetCategoriesData: AssetCategoriesData) =>
+ assetCategoriesData.assetCategories.reduce((acc, cur) => acc + cur.count, 0);
+
export const AssetTypeList = (props: AssetTypeListProps) => {
const [data, setData] = useState();
const previousData = usePrevious(data);
@@ -109,7 +112,7 @@ export const AssetTypeList = (props: AssetTypeListProps) => {
useEffect(() => {
props.setRefresher(refreshData);
- }, [refreshData]);
+ }, [refreshData, props.setRefresher]);
const areAnyFiltersApplied = checkIfAnyFiltersApplied(
isComplexFilterEnabled,
@@ -141,6 +144,12 @@ export const AssetTypeList = (props: AssetTypeListProps) => {
};
}, []);
+ useEffect(() => {
+ if (data && previousData !== data) {
+ props.onAssetCountChange(getAssetCount(data));
+ }
+ }, [previousData, data, props.onAssetCountChange]);
+
useEffect(() => {
if (
(isEnvironment(previousEnvironment) &&
diff --git a/src/components/Assets/AssetTypeList/styles.ts b/src/components/Assets/AssetTypeList/styles.ts
index fd035dddd..2f2e0ef59 100644
--- a/src/components/Assets/AssetTypeList/styles.ts
+++ b/src/components/Assets/AssetTypeList/styles.ts
@@ -6,13 +6,4 @@ export const List = styled.ul`
gap: 8px;
padding: 0 8px 8px;
margin: 0;
- color: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#828797";
- case "dark":
- case "dark-jetbrains":
- return "#dadada";
- }
- }};
`;
diff --git a/src/components/Assets/AssetTypeList/types.ts b/src/components/Assets/AssetTypeList/types.ts
index d54f1d8a6..92c5a4110 100644
--- a/src/components/Assets/AssetTypeList/types.ts
+++ b/src/components/Assets/AssetTypeList/types.ts
@@ -11,6 +11,7 @@ export interface AssetTypeListProps {
searchQuery: string;
scopeViewOptions: AssetScopeOption | null;
setRefresher: (refresher: () => void) => void;
+ onAssetCountChange: (count: number) => void;
}
export interface AssetCategoriesData {
diff --git a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx
index 8571043c7..330a6d595 100644
--- a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx
+++ b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx
@@ -17,5 +17,25 @@ export default meta;
type Story = StoryObj;
export const Default: Story = {
- args: {}
+ args: {
+ currentScope: {
+ span: {
+ displayName: "displayName",
+ spanCodeObjectId: "spanCodeObjectId",
+ serviceName: null,
+ role: "Entry"
+ },
+ code: {
+ relatedCodeDetailsList: [],
+ codeDetailsList: []
+ },
+ hasErrors: false,
+ issuesInsightsCount: 0,
+ analyticsInsightsCount: 0
+ },
+ assetsCount: 1,
+ onAssetViewChange: () => {
+ return undefined;
+ }
+ }
};
diff --git a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx
index a9e32b6b9..85b0634b8 100644
--- a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx
+++ b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx
@@ -1,44 +1,71 @@
import { useEffect, useState } from "react";
-import { ToggleSwitch } from "../../common/ToggleSwitch";
+import { isNumber } from "../../../typeGuards/isNumber";
+import { ArrowIcon } from "../../common/icons/12px/ArrowIcon";
+import { TreeNodesIcon } from "../../common/icons/12px/TreeNodesIcon";
+import { Toggle } from "../../common/v3/Toggle";
+import { ToggleOption } from "../../common/v3/Toggle/types";
import * as s from "./styles";
import { AssetsViewConfigurationProps as AssetsViewScopeConfigurationProps } from "./types";
-export const AssetsViewScopeConfiguration = (
- props: AssetsViewScopeConfigurationProps
-) => {
- const [isEntry, setIsEntry] = useState(false);
- const [isDirect, setIsDirect] = useState(false);
- const scope = props.currentScope;
+type ViewMode = "descendants" | "children";
+
+export const AssetsViewScopeConfiguration = ({
+ currentScope,
+ onAssetViewChange,
+ assetsCount
+}: AssetsViewScopeConfigurationProps) => {
+ const [viewMode, setViewMode] = useState("descendants");
useEffect(() => {
- const isEntryPoint = !scope || scope.span?.role === "Entry";
- props.onAssetViewChanged({
- scopedSpanCodeObjectId: scope?.span?.spanCodeObjectId,
+ const isEntryPoint = !currentScope || currentScope.span?.role === "Entry";
+
+ setViewMode(isEntryPoint ? "children" : "descendants");
+
+ onAssetViewChange({
+ scopedSpanCodeObjectId: currentScope?.span?.spanCodeObjectId,
isDirect: !isEntryPoint
});
- setIsEntry(isEntryPoint);
- setIsDirect(!isEntryPoint);
- }, [scope]);
+ }, [currentScope, onAssetViewChange]);
+
+ const handleToggleOptionChange = (value: ViewMode) => {
+ setViewMode(value);
+
+ onAssetViewChange &&
+ onAssetViewChange({
+ isDirect: value === "children",
+ scopedSpanCodeObjectId: currentScope?.span?.spanCodeObjectId
+ });
+ };
+
+ const toggleOptions: ToggleOption[] = [
+ {
+ value: "descendants",
+ icon: TreeNodesIcon
+ },
+ {
+ value: "children",
+ icon: ArrowIcon
+ }
+ ];
+
+ const assetTypeDescription =
+ viewMode === "descendants" ? "descendant" : "child";
return (
- Assets filtered to current scope
-
- {
- setIsDirect(val);
- props.onAssetViewChanged &&
- props.onAssetViewChanged({
- isDirect: val,
- scopedSpanCodeObjectId: scope?.span?.spanCodeObjectId
- });
- }}
- checked={isDirect}
- disabled={!isEntry}
- />
-
+
+
+ Showing
+
+ {isNumber(assetsCount) ? ` ${assetsCount} ` : " "}
+
+ {assetTypeDescription} asset{assetsCount === 1 ? "" : "s"}
+
);
};
diff --git a/src/components/Assets/AssetsViewScopeConfiguration/styles.ts b/src/components/Assets/AssetsViewScopeConfiguration/styles.ts
index 7f88637c5..d898467bf 100644
--- a/src/components/Assets/AssetsViewScopeConfiguration/styles.ts
+++ b/src/components/Assets/AssetsViewScopeConfiguration/styles.ts
@@ -1,12 +1,18 @@
import styled from "styled-components";
+import { subscriptRegularTypography } from "../../common/App/typographies";
export const Container = styled.div`
display: flex;
- font-size: 14px;
- flex-grow: 1;
- justify-content: space-between;
+ gap: 8px;
+ align-items: center;
`;
-export const Item = styled.div`
- display: flex;
+export const Label = styled.div`
+ ${subscriptRegularTypography}
+
+ color: ${({ theme }) => theme.colors.v3.text.tertiary};
+`;
+
+export const AssetsCount = styled.span`
+ color: ${({ theme }) => theme.colors.v3.text.primary};
`;
diff --git a/src/components/Assets/AssetsViewScopeConfiguration/types.ts b/src/components/Assets/AssetsViewScopeConfiguration/types.ts
index 09e48dcce..da43b8398 100644
--- a/src/components/Assets/AssetsViewScopeConfiguration/types.ts
+++ b/src/components/Assets/AssetsViewScopeConfiguration/types.ts
@@ -1,8 +1,9 @@
import { Scope } from "../../common/App/types";
export interface AssetsViewConfigurationProps {
- onAssetViewChanged: (assetViewScope: AssetScopeOption) => void;
+ onAssetViewChange: (assetViewScope: AssetScopeOption) => void;
currentScope: Scope;
+ assetsCount?: number;
}
export interface AssetScopeOption {
diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx
index 15d0ea0b6..94f0b5d5e 100644
--- a/src/components/Assets/index.tsx
+++ b/src/components/Assets/index.tsx
@@ -1,4 +1,4 @@
-import { useContext, useEffect, useMemo, useState } from "react";
+import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { lt, valid } from "semver";
import {
featureFlagMinBackendVersions,
@@ -26,6 +26,7 @@ import { trackingEvents } from "./tracking";
import { DataRefresher } from "./types";
export const Assets = () => {
+ const [assetsCount, setAssetsCount] = useState();
const [selectedAssetTypeId, setSelectedAssetTypeId] = useState(
null
);
@@ -113,6 +114,18 @@ export const Assets = () => {
currentRefresher && currentRefresher.refresh();
};
+ const handleAssetCountChange = useCallback((count: number) => {
+ setAssetsCount(count);
+ }, []);
+
+ const handleAssetViewModeChange = useCallback((val: AssetScopeOption) => {
+ setAssetScopeOption(val);
+ }, []);
+
+ const handleRefresherChange = useCallback((refresher: () => void) => {
+ setAssetTypeListRefresher({ refresh: refresher });
+ }, []);
+
const renderContent = () => {
if (isBackendUpgradeMessageVisible) {
return (
@@ -145,9 +158,8 @@ export const Assets = () => {
filters={selectedFilters}
searchQuery={debouncedSearchInputValue}
scopeViewOptions={assetScopeOption}
- setRefresher={(refresher) => {
- setAssetTypeListRefresher({ refresh: refresher });
- }}
+ setRefresher={handleRefresherChange}
+ onAssetCountChange={handleAssetCountChange}
/>
);
}
@@ -160,9 +172,8 @@ export const Assets = () => {
filters={selectedFilters}
searchQuery={debouncedSearchInputValue}
scopeViewOptions={assetScopeOption}
- setRefresher={(refresher) => {
- setAssetListRefresher({ refresh: refresher });
- }}
+ setRefresher={handleRefresherChange}
+ onAssetCountChange={handleAssetCountChange}
/>
);
};
@@ -170,6 +181,15 @@ export const Assets = () => {
return (
+ {config.scope && config.scope.span && (
+
+
+
+ )}
{window.assetsSearch === true && (
{
{config.scope && config.scope.span && (
-
- {
- setAssetScopeOption(val);
- }}
- />
-
+ Assets filtered to current scope
)}
{renderContent()}
diff --git a/src/components/Assets/styles.ts b/src/components/Assets/styles.ts
index e305121ce..1e223d025 100644
--- a/src/components/Assets/styles.ts
+++ b/src/components/Assets/styles.ts
@@ -1,20 +1,11 @@
import styled from "styled-components";
-import { grayScale } from "../common/App/v2colors";
import { Button } from "../common/v3/Button";
export const Container = styled.div`
height: 100%;
display: flex;
flex-direction: column;
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return "#fbfdff";
- case "dark":
- case "dark-jetbrains":
- return "#2b2d30";
- }
- }};
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
`;
export const Header = styled.div`
@@ -64,16 +55,8 @@ export const SearchInput = styled.input`
padding: 4px 4px 4px 20px;
border-radius: 4px;
outline: none;
- border: 1px solid ${({ theme }) => theme.colors.stroke.primary};
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return grayScale[50];
- case "dark":
- case "dark-jetbrains":
- return grayScale[1000];
- }
- }};
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.dark};
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
box-shadow: 1px 1px 4px 0 rgb(0 0 0 / 25%);
caret-color: ${({ theme }) => {
switch (theme.mode) {
@@ -126,16 +109,8 @@ export const UpgradeMessage = styled.div`
export const RefreshButton = styled(Button)`
color: ${({ theme }) => theme.colors.v3.icon.tertiary};
- border: 1px solid ${({ theme }) => theme.colors.stroke.primary};
- background: ${({ theme }) => {
- switch (theme.mode) {
- case "light":
- return grayScale[50];
- case "dark":
- case "dark-jetbrains":
- return grayScale[1000];
- }
- }};
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.dark};
+ background: ${({ theme }) => theme.colors.v3.surface.primary};
&:hover:enabled {
color: ${({ theme }) => theme.colors.v3.icon.white};
diff --git a/src/components/common/icons/12px/ArrowIcon.tsx b/src/components/common/icons/12px/ArrowIcon.tsx
new file mode 100644
index 000000000..e1e445c4b
--- /dev/null
+++ b/src/components/common/icons/12px/ArrowIcon.tsx
@@ -0,0 +1,47 @@
+import React from "react";
+import { useIconProps } from "../hooks";
+import { Direction, RotatableIconProps } from "../types";
+
+const directionRotateMap: { [key in Direction]: string } = {
+ [Direction.DOWN]: "0",
+ [Direction.LEFT]: "90",
+ [Direction.UP]: "180",
+ [Direction.RIGHT]: "270"
+};
+
+const ArrowIconComponent = (props: RotatableIconProps) => {
+ const { size, color } = useIconProps(props);
+
+ const transform = {
+ transform: `rotate(${
+ directionRotateMap[props.direction || Direction.DOWN]
+ })`
+ };
+
+ return (
+
+ );
+};
+
+export const ArrowIcon = React.memo(ArrowIconComponent);
diff --git a/src/components/common/icons/12px/TreeNodesIcon.tsx b/src/components/common/icons/12px/TreeNodesIcon.tsx
new file mode 100644
index 000000000..178db55ac
--- /dev/null
+++ b/src/components/common/icons/12px/TreeNodesIcon.tsx
@@ -0,0 +1,34 @@
+import React from "react";
+import { useIconProps } from "../hooks";
+import { IconProps } from "../types";
+
+const TreeNodesIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const TreeNodesIcon = React.memo(TreeNodesIconComponent);
diff --git a/src/components/common/v3/Toggle/index.tsx b/src/components/common/v3/Toggle/index.tsx
index fa321afbe..ed20de331 100644
--- a/src/components/common/v3/Toggle/index.tsx
+++ b/src/components/common/v3/Toggle/index.tsx
@@ -2,6 +2,8 @@ import * as s from "./styles";
import { ToggleProps, ToggleValue } from "./types";
export const Toggle = (props: ToggleProps) => {
+ const size = props.size || "large";
+
const handleOptionButtonClick = (value: T) => {
props.onValueChange(value);
};
@@ -10,11 +12,13 @@ export const Toggle = (props: ToggleProps) => {
{props.options.map((option) => (
handleOptionButtonClick(option.value)}
>
{option.label}
+ {option.icon && }
))}
diff --git a/src/components/common/v3/Toggle/styles.ts b/src/components/common/v3/Toggle/styles.ts
index ddd4ccb5e..21e8a92aa 100644
--- a/src/components/common/v3/Toggle/styles.ts
+++ b/src/components/common/v3/Toggle/styles.ts
@@ -15,13 +15,14 @@ export const Container = styled.div`
export const OptionButton = styled.button`
${footnoteRegularTypography}
+ display: flex;
font-family: inherit;
border: none;
outline: none;
- border-radius: 4px;
- padding: 2px 4px;
cursor: pointer;
user-select: none;
+ 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};
background: ${({ theme, $selected }) =>
diff --git a/src/components/common/v3/Toggle/types.ts b/src/components/common/v3/Toggle/types.ts
index bd726acb1..1fe9c4010 100644
--- a/src/components/common/v3/Toggle/types.ts
+++ b/src/components/common/v3/Toggle/types.ts
@@ -1,16 +1,23 @@
+import { ComponentType } from "react";
+import { IconProps } from "../../icons/types";
+
export type ToggleValue = string | number;
+export type ToggleSize = "small" | "large";
export interface ToggleOption {
value: T;
- label: string;
+ label?: string;
+ icon?: ComponentType;
}
export interface ToggleProps {
options: ToggleOption[];
onValueChange: (value: T) => void;
value: T;
+ size?: ToggleSize;
}
export interface OptionButtonProps {
$selected: boolean;
+ $size: ToggleSize;
}