diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7850d719c..8bcdf7d9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Lint & build on: push: branches: ["main"] - # Github Actions do not support YAML anchors yet,so we have to repeat + # Github Actions don't support YAML anchors yet, so we have to repeat # the paths-ignore in both push and pull_request events. # More info: https://github.com/actions/runner/issues/1182 paths-ignore: diff --git a/src/actions.ts b/src/actions.ts index 594902b55..d2539944f 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -20,7 +20,6 @@ export const actions = addPrefix(ACTION_PREFIX, { OPEN_DOCUMENTATION: "OPEN_DOCUMENTATION", SET_DIGMA_API_URL: "SET_DIGMA_API_URL", SET_USER_REGISTRATION_EMAIL: "SET_USER_REGISTRATION_EMAIL", - SET_ENVIRONMENT: "SET_ENVIRONMENT", SET_IS_OBSERVABILITY_ENABLED: "SET_IS_OBSERVABILITY_ENABLED", SET_OBSERVABILITY: "SET_OBSERVABILITY", GET_BACKEND_INFO: "GET_BACKEND_INFO", diff --git a/src/components/Assets/AssetList/AssetList.stories.tsx b/src/components/Assets/AssetList/AssetList.stories.tsx index f376e16ad..9b1303009 100644 --- a/src/components/Assets/AssetList/AssetList.stories.tsx +++ b/src/components/Assets/AssetList/AssetList.stories.tsx @@ -38,7 +38,6 @@ const mockedConfig: ConfigContextData = { export const Default: Story = { args: { - searchQuery: "", setRefresher: () => { return undefined; }, @@ -64,7 +63,6 @@ export const WithPerformanceImpact: Story = { ) ], args: { - searchQuery: "", setRefresher: () => { return undefined; }, diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx index 3e15c86e0..6c9d893d3 100644 --- a/src/components/Assets/AssetList/index.tsx +++ b/src/components/Assets/AssetList/index.tsx @@ -3,7 +3,9 @@ import { DefaultTheme, useTheme } from "styled-components"; import { DigmaMessageError } from "../../../api/types"; import { dispatcher } from "../../../dispatcher"; import { usePrevious } from "../../../hooks/usePrevious"; +import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; 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"; @@ -19,6 +21,7 @@ import { ChevronIcon } from "../../common/icons/ChevronIcon"; import { SortIcon } from "../../common/icons/SortIcon"; import { Direction } from "../../common/icons/types"; import { AssetFilterQuery } from "../AssetsFilter/types"; +import { ViewMode } from "../AssetsViewScopeConfiguration/types"; import { actions } from "../actions"; import { trackingEvents } from "../tracking"; import { checkIfAnyFiltersApplied, getAssetTypeInfo } from "../utils"; @@ -92,9 +95,9 @@ const getData = ( page: number, sorting: Sorting, searchQuery: string, - filters: AssetFilterQuery = { services: [], operations: [], insights: [] }, - isDirect?: boolean, - scopedSpanCodeObjectId?: string + filters: AssetFilterQuery, + viewMode: ViewMode, + scopeSpanCodeObjectId?: string ) => { window.sendMessageToDigma({ action: actions.GET_DATA, @@ -105,36 +108,41 @@ const getData = ( pageSize: PAGE_SIZE, sortBy: sorting.criterion, sortOrder: sorting.order, - directOnly: isDirect, - scopedSpanCodeObjectId, + directOnly: viewMode === "children", + scopedSpanCodeObjectId: scopeSpanCodeObjectId, ...(searchQuery.length > 0 ? { displayName: searchQuery } : {}), - ...(scopedSpanCodeObjectId ? { ...filters, services: [] } : filters) + ...(scopeSpanCodeObjectId ? { ...filters, services: [] } : filters) } } }); }; export const AssetList = ({ - searchQuery, assetTypeId, - filters, - scopeViewOptions, onAssetCountChange, setRefresher, onGoToAllAssets }: AssetListProps) => { - const [data, setData] = useState(); + const { + assets: data, + sorting, + page, + viewMode, + search, + filters + } = useAssetsSelector(); + const { + setAssets: setData, + setAssetsSorting: setSorting, + setAssetsPage: setPage, + setShowAssetsHeaderToolBox + } = useStore.getState(); const previousData = usePrevious(data); const [lastSetDataTimeStamp, setLastSetDataTimeStamp] = useState(); const previousLastSetDataTimeStamp = usePrevious(lastSetDataTimeStamp); - const [sorting, setSorting] = useState({ - criterion: SORTING_CRITERION.CRITICAL_INSIGHTS, - order: SORTING_ORDER.DESC - }); const [isSortingMenuOpen, setIsSortingMenuOpen] = useState(false); const theme = useTheme(); const sortingMenuChevronColor = getSortingMenuChevronColor(theme); - const [page, setPage] = useState(0); const filteredCount = data?.filteredCount ?? 0; const pageStartItemNumber = page * PAGE_SIZE + 1; const pageEndItemNumber = Math.min( @@ -142,29 +150,32 @@ export const AssetList = ({ filteredCount ); const listRef = useRef(null); - const { environment, backendInfo, scope } = useConfigSelector(); const refreshTimerId = useRef(); + const { environment, backendInfo, scope } = useConfigSelector(); const previousEnvironment = usePrevious(environment); - const previousViewScope = usePrevious(scopeViewOptions); - const isServicesFilterEnabled = !scope?.span?.spanCodeObjectId; + const previousViewMode = usePrevious(viewMode); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const previousScopeSpanCodeObjectId = usePrevious(scopeSpanCodeObjectId); + const isServicesFilterEnabled = !scopeSpanCodeObjectId; + const isInitialLoading = !data; const refreshData = useCallback(() => { getData( assetTypeId, page, sorting, - searchQuery, + search, filters, - scopeViewOptions?.isDirect, - scopeViewOptions?.scopedSpanCodeObjectId + viewMode, + scopeSpanCodeObjectId ); }, [ page, assetTypeId, filters, - scopeViewOptions?.isDirect, - scopeViewOptions?.scopedSpanCodeObjectId, - searchQuery, + viewMode, + scopeSpanCodeObjectId, + search, sorting ]); @@ -181,12 +192,10 @@ export const AssetList = ({ const areAnyFiltersApplied = checkIfAnyFiltersApplied( filters, - searchQuery, + search, isServicesFilterEnabled ); - const isInitialLoading = !data; - useEffect(() => { refreshData(); }, [refreshData]); @@ -204,12 +213,13 @@ export const AssetList = ({ }; dispatcher.addActionListener(actions.SET_DATA, handleAssetsData); + setShowAssetsHeaderToolBox(true); return () => { dispatcher.removeActionListener(actions.SET_DATA, handleAssetsData); window.clearTimeout(refreshTimerId.current); }; - }, []); + }, [setData, setShowAssetsHeaderToolBox]); useEffect(() => { if (data && previousData?.filteredCount !== data?.filteredCount) { @@ -225,15 +235,18 @@ export const AssetList = ({ if ( (isEnvironment(previousEnvironment) && previousEnvironment.id !== environment?.id) || - previousViewScope !== scopeViewOptions + viewMode !== previousViewMode || + previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId ) { refreshData(); } }, [ environment?.id, previousEnvironment, - previousViewScope, - scopeViewOptions, + previousViewMode, + viewMode, + scopeSpanCodeObjectId, + previousScopeSpanCodeObjectId, refreshData ]); @@ -256,21 +269,30 @@ export const AssetList = ({ order: SORTING_ORDER.DESC }); } - }, [isImpactHidden, sorting]); + }, [isImpactHidden, sorting, setSorting]); useEffect(() => { setPage(0); - }, [environment?.id, searchQuery, sorting, assetTypeId, scopeViewOptions]); + }, [ + environment?.id, + search, + sorting, + assetTypeId, + viewMode, + scopeSpanCodeObjectId, + setPage + ]); useEffect(() => { listRef.current?.scrollTo(0, 0); }, [ environment?.id, - searchQuery, + search, sorting, page, assetTypeId, - scopeViewOptions + viewMode, + scopeSpanCodeObjectId ]); const handleAllAssetsLinkClick = () => { diff --git a/src/components/Assets/AssetList/types.ts b/src/components/Assets/AssetList/types.ts index 5adc2d363..5e8918742 100644 --- a/src/components/Assets/AssetList/types.ts +++ b/src/components/Assets/AssetList/types.ts @@ -1,13 +1,8 @@ import { Duration } from "../../../globals"; -import { AssetFilterQuery } from "../AssetsFilter/types"; -import { AssetScopeOption } from "../AssetsViewScopeConfiguration/types"; export interface AssetListProps { onGoToAllAssets: () => void; assetTypeId: string; - filters?: AssetFilterQuery; - searchQuery: string; - scopeViewOptions: AssetScopeOption | null; setRefresher: (refresher: () => void) => void; onAssetCountChange: (count: number) => void; } diff --git a/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx b/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx index 57266a477..a2b600cb4 100644 --- a/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx +++ b/src/components/Assets/AssetTypeList/AssetTypeList.stories.tsx @@ -22,8 +22,7 @@ export const Default: Story = { args: { setRefresher: () => { return undefined; - }, - searchQuery: "" + } }, play: () => { window.setTimeout(() => { @@ -65,7 +64,6 @@ export const Default: Story = { export const Empty: Story = { args: { - searchQuery: "", setRefresher: () => { return undefined; } @@ -82,3 +80,36 @@ export const Empty: Story = { }, 0); } }; + +export const EmptyWithParents: Story = { + args: { + 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..aec78e61e 100644 --- a/src/components/Assets/AssetTypeList/index.tsx +++ b/src/components/Assets/AssetTypeList/index.tsx @@ -2,13 +2,21 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { DigmaMessageError } from "../../../api/types"; import { dispatcher } from "../../../dispatcher"; import { usePrevious } from "../../../hooks/usePrevious"; +import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; 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 { ViewMode } from "../AssetsViewScopeConfiguration/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"; @@ -31,18 +39,18 @@ export const ASSET_TYPE_IDS = [ ]; const getData = ( - filters: AssetFilterQuery = { services: [], operations: [], insights: [] }, + filters: AssetFilterQuery, searchQuery: string, - isDirect?: boolean, - scopedSpanCodeObjectId?: string + viewMode: ViewMode, + scopeSpanCodeObjectId?: string ) => { window.sendMessageToDigma({ action: actions.GET_CATEGORIES_DATA, payload: { query: { - directOnly: isDirect, - scopedSpanCodeObjectId, - ...(scopedSpanCodeObjectId + directOnly: viewMode === "children", + scopedSpanCodeObjectId: scopeSpanCodeObjectId, + ...(scopeSpanCodeObjectId ? { ...filters, services: [] @@ -58,40 +66,36 @@ const getAssetCount = (assetCategoriesData: AssetCategoriesData) => assetCategoriesData.assetCategories.reduce((acc, cur) => acc + cur.count, 0); export const AssetTypeList = ({ - filters, - searchQuery, - scopeViewOptions, setRefresher, onAssetCountChange, onAssetTypeSelect }: AssetTypeListProps) => { - const [data, setData] = useState(); + const { + search, + viewMode, + filters, + assetCategoriesData: data + } = useAssetsSelector(); + const { setAssetCategoriesData: setData, setShowAssetsHeaderToolBox } = + useStore.getState(); + const previousSearch = usePrevious(search); + const previousViewMode = usePrevious(viewMode); const previousData = usePrevious(data); const [lastSetDataTimeStamp, setLastSetDataTimeStamp] = useState(); const previousLastSetDataTimeStamp = usePrevious(lastSetDataTimeStamp); const { scope, environment } = useConfigSelector(); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const previousScopeSpanCodeObjectId = usePrevious(scopeSpanCodeObjectId); const previousEnvironment = usePrevious(environment); const refreshTimerId = useRef(); - const previousSearchQuery = usePrevious(searchQuery); - const previousViewScope = usePrevious(scopeViewOptions); const isServicesFilterEnabled = !scope?.span?.spanCodeObjectId; + const [showNoDataWithParents, setShowNoDataWithParents] = useState(false); const isInitialLoading = !data; const refreshData = useCallback( - () => - getData( - filters, - searchQuery, - scopeViewOptions?.isDirect, - scopeViewOptions?.scopedSpanCodeObjectId - ), - [ - filters, - scopeViewOptions?.isDirect, - scopeViewOptions?.scopedSpanCodeObjectId, - searchQuery - ] + () => getData(filters, search, viewMode, scopeSpanCodeObjectId), + [filters, scopeSpanCodeObjectId, viewMode, search] ); useEffect(() => { @@ -100,7 +104,7 @@ export const AssetTypeList = ({ const areAnyFiltersApplied = checkIfAnyFiltersApplied( filters, - searchQuery, + search, isServicesFilterEnabled ); @@ -132,30 +136,40 @@ export const AssetTypeList = ({ ); window.clearTimeout(refreshTimerId.current); }; - }, []); + }, [setData]); 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 ( (isEnvironment(previousEnvironment) && previousEnvironment.id !== environment?.id) || - (isString(previousSearchQuery) && previousSearchQuery !== searchQuery) || - previousViewScope !== scopeViewOptions + (isString(previousSearch) && previousSearch !== search) || + previousViewMode !== viewMode || + previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId ) { refreshData(); } }, [ environment?.id, previousEnvironment, - previousSearchQuery, - previousViewScope, - scopeViewOptions, - searchQuery, + search, + previousSearch, + previousScopeSpanCodeObjectId, + scopeSpanCodeObjectId, + viewMode, + previousViewMode, refreshData ]); @@ -172,6 +186,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 +205,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..522b52510 100644 --- a/src/components/Assets/AssetTypeList/types.ts +++ b/src/components/Assets/AssetTypeList/types.ts @@ -1,13 +1,9 @@ import { MemoExoticComponent } from "react"; +import { SpanInfo } from "../../../types"; import { IconProps } from "../../common/icons/types"; -import { AssetFilterQuery } from "../AssetsFilter/types"; -import { AssetScopeOption } from "../AssetsViewScopeConfiguration/types"; export interface AssetTypeListProps { onAssetTypeSelect: (assetTypeId: string) => void; - filters?: AssetFilterQuery; - searchQuery: string; - scopeViewOptions: AssetScopeOption | null; setRefresher: (refresher: () => void) => void; onAssetCountChange: (count: number) => void; } @@ -17,6 +13,7 @@ export interface AssetCategoriesData { name: string; count: number; }[]; + parents?: SpanInfo[]; } export interface AssetCategoryData { diff --git a/src/components/Assets/AssetsFilter/index.tsx b/src/components/Assets/AssetsFilter/index.tsx index c234267d3..dfae3da2e 100644 --- a/src/components/Assets/AssetsFilter/index.tsx +++ b/src/components/Assets/AssetsFilter/index.tsx @@ -1,13 +1,15 @@ -import { ComponentType, useEffect, useState } from "react"; +import { ComponentType, useEffect, useMemo, useState } from "react"; import { dispatcher } from "../../../dispatcher"; +import { getFeatureFlagValue } from "../../../featureFlags"; import { usePersistence } from "../../../hooks/usePersistence"; import { usePrevious } from "../../../hooks/usePrevious"; +import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; import { useConfigSelector } from "../../../store/config/useConfigSelector"; import { useStore } from "../../../store/useStore"; import { isEnvironment } from "../../../typeGuards/isEnvironment"; import { isNull } from "../../../typeGuards/isNull"; import { isUndefined } from "../../../typeGuards/isUndefined"; -import { InsightType } from "../../../types"; +import { FeatureFlag, InsightType } from "../../../types"; import { sendTrackingEvent } from "../../../utils/actions/sendTrackingEvent"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; import { getInsightTypeInfo } from "../../../utils/getInsightTypeInfo"; @@ -16,15 +18,14 @@ import { WrenchIcon } from "../../common/icons/12px/WrenchIcon"; import { EndpointIcon } from "../../common/icons/EndpointIcon"; import { SparkleIcon } from "../../common/icons/SparkleIcon"; import { IconProps } from "../../common/icons/types"; -import { AssetScopeOption } from "../AssetsViewScopeConfiguration/types"; import { actions } from "../actions"; import { trackingEvents } from "../tracking"; import * as s from "./styles"; import { AssetFilterCategory, AssetFilterQuery, - AssetsFilterProps, AssetsFiltersData, + GetAssetFiltersDataParams, GetAssetFiltersDataPayload } from "./types"; @@ -34,24 +35,19 @@ const getData = ({ services, operations, insights, - assetScopeOption, + viewMode, + scopeSpanCodeObjectId, searchQuery -}: { - services: string[]; - operations: string[]; - insights: InsightType[]; - assetScopeOption: AssetScopeOption | null; - searchQuery: string; -}) => { +}: GetAssetFiltersDataParams) => { window.sendMessageToDigma({ action: actions.GET_ASSET_FILTERS_DATA, payload: { query: { - services, + services: scopeSpanCodeObjectId ? services : [], operations, insights, - directOnly: Boolean(assetScopeOption?.isDirect), - scopedSpanCodeObjectId: assetScopeOption?.scopedSpanCodeObjectId, + directOnly: viewMode === "children", + scopedSpanCodeObjectId: scopeSpanCodeObjectId, ...(searchQuery?.length > 0 ? { displayName: searchQuery } : {}) } } @@ -88,23 +84,22 @@ const renderFilterCategory = ( ); }; -export const AssetsFilter = ({ - onApply, - filters, - assetScopeOption, - searchQuery -}: AssetsFilterProps) => { - const [data, setData] = useState<{ data: AssetsFiltersData | null }>(); +export const AssetsFilter = () => { + const [data, setData] = useState(); const previousData = usePrevious(data); + const { filters, search: searchQuery, viewMode } = useAssetsSelector(); + const { + setAssetsFilters: setFilters, + setSelectedServices: setGloballySelectedServices + } = useStore.getState(); const [isOpen, setIsOpen] = useState(false); const previousIsOpen = usePrevious(isOpen); const { selectedServices: globallySelectedServices, environment, - scope + scope, + backendInfo } = useConfigSelector(); - const { setSelectedServices: setGloballySelectedServices } = - useStore.getState(); const [persistedFilters, setPersistedFilters] = usePersistence(PERSISTENCE_KEY, "project"); const previousPersistedFilters = usePrevious(persistedFilters); @@ -118,6 +113,34 @@ export const AssetsFilter = ({ const [selectedInsights, setSelectedInsights] = useState([]); const previousEnvironment = usePrevious(environment); const previousScope = usePrevious(scope); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const areExtendedAssetsFiltersEnabled = getFeatureFlagValue( + backendInfo, + FeatureFlag.ARE_EXTENDED_ASSETS_FILTERS_ENABLED + ); + + const query = useMemo( + () => ({ + services: isServicesFilterEnabled ? selectedServices : [], + operations: filters.operations, + insights: filters.insights as InsightType[], + viewMode: areExtendedAssetsFiltersEnabled ? viewMode : undefined, + scopeSpanCodeObjectId: areExtendedAssetsFiltersEnabled + ? scopeSpanCodeObjectId + : undefined, + searchQuery: areExtendedAssetsFiltersEnabled ? searchQuery : "" + }), + [ + isServicesFilterEnabled, + selectedServices, + filters.operations, + filters.insights, + viewMode, + searchQuery, + scopeSpanCodeObjectId, + areExtendedAssetsFiltersEnabled + ] + ); // Get data after filters have been rehydrated useEffect(() => { @@ -126,6 +149,7 @@ export const AssetsFilter = ({ !isUndefined(persistedFilters) ) { getData({ + ...query, services: isServicesFilterEnabled ? selectedServices : [], operations: persistedFilters?.operations ?? [ ...selectedEndpoints, @@ -133,9 +157,7 @@ export const AssetsFilter = ({ ...selectedInternals ], insights: - (persistedFilters?.insights as InsightType[]) || selectedInsights, - assetScopeOption, - searchQuery + (persistedFilters?.insights as InsightType[]) || selectedInsights }); } }, [ @@ -146,17 +168,15 @@ export const AssetsFilter = ({ selectedConsumers, selectedInternals, selectedInsights, - scope, - assetScopeOption, - searchQuery, - globallySelectedServices, - isServicesFilterEnabled + isServicesFilterEnabled, + query ]); + // Handle filters data response useEffect(() => { const handleData = (data: unknown) => { const filtersData = data as AssetsFiltersData | null; - setData({ data: filtersData }); + setData(filtersData); }; dispatcher.addActionListener(actions.SET_ASSET_FILTERS_DATA, handleData); @@ -180,31 +200,33 @@ export const AssetsFilter = ({ operations: [], insights: [] }; + setFilters(defaultFilters); setPersistedFilters(defaultFilters); if (isServicesFilterEnabled) { setGloballySelectedServices(defaultFilters.services); } - onApply(defaultFilters); getData({ - ...defaultFilters, - assetScopeOption, - searchQuery + ...query, + ...defaultFilters }); } }, [ + setFilters, previousEnvironment, environment, setPersistedFilters, - onApply, - assetScopeOption, - searchQuery, isServicesFilterEnabled, - setGloballySelectedServices + setGloballySelectedServices, + areExtendedAssetsFiltersEnabled, + query ]); // Clear filters and get data when scope is changed, but keep selected services useEffect(() => { - if (previousScope && previousScope !== scope) { + if ( + previousScope && + previousScope.span?.spanCodeObjectId !== scopeSpanCodeObjectId + ) { const newFilters = { services: selectedServices, operations: [], @@ -214,68 +236,45 @@ export const AssetsFilter = ({ if (isServicesFilterEnabled) { setGloballySelectedServices(newFilters.services); } - onApply(newFilters); + setFilters(newFilters); getData({ - ...newFilters, - assetScopeOption, - searchQuery + ...query, + ...newFilters }); } }, [ + setFilters, setPersistedFilters, setGloballySelectedServices, - onApply, previousScope, - scope, - assetScopeOption, - searchQuery, + scopeSpanCodeObjectId, selectedServices, - isServicesFilterEnabled + isServicesFilterEnabled, + areExtendedAssetsFiltersEnabled, + query ]); // Get data when the popover is opened useEffect(() => { if (isOpen && !previousIsOpen) { - getData({ - services: isServicesFilterEnabled ? selectedServices : [], - operations: [ - ...selectedEndpoints, - ...selectedConsumers, - ...selectedInternals - ], - insights: selectedInsights, - assetScopeOption, - searchQuery - }); + getData(query); } - }, [ - isOpen, - previousIsOpen, - scope, - selectedConsumers, - selectedEndpoints, - selectedInsights, - selectedInternals, - selectedServices, - assetScopeOption, - searchQuery, - isServicesFilterEnabled - ]); + }, [isOpen, previousIsOpen, query]); // Apply filters when data is loaded useEffect(() => { - if (previousData === data || isNull(data?.data)) { + if (previousData === data || isNull(data)) { return; } const servicesToSelect = - data?.data?.categories + data?.categories .find((x) => x.categoryName === "Services") ?.entries?.filter((x) => x.selected) .map((x) => x.name) ?? []; setSelectedServices(servicesToSelect); - const operationsCategory = data?.data?.categories.find( + const operationsCategory = data?.categories.find( (x) => x.categoryName === "Operations" ); @@ -300,7 +299,7 @@ export const AssetsFilter = ({ .map((x) => x.name) ?? []; setSelectedInternals(internalsToSelect); - const insightsToSelect = (data?.data?.categories + const insightsToSelect = (data?.categories .find((x) => x.categoryName === "Insights") ?.entries?.filter((x) => x.selected) .map((x) => x.name) ?? []) as InsightType[]; @@ -321,13 +320,13 @@ export const AssetsFilter = ({ if (isServicesFilterEnabled) { setGloballySelectedServices(filtersQuery.services); } - onApply(filtersQuery); + setFilters(filtersQuery); } }, [ previousData, data, filters, - onApply, + setFilters, selectedServices, selectedEndpoints, selectedConsumers, @@ -342,7 +341,7 @@ export const AssetsFilter = ({ useEffect(() => { if (previousIsOpen && !isOpen) { const filtersQuery = { - services: selectedServices, + services: isServicesFilterEnabled ? selectedServices : [], operations: [ ...selectedEndpoints, ...selectedConsumers, @@ -350,7 +349,7 @@ export const AssetsFilter = ({ ], insights: selectedInsights }; - onApply(filtersQuery); + setFilters(filtersQuery); setPersistedFilters(filtersQuery); if (isServicesFilterEnabled) { setGloballySelectedServices(filtersQuery.services); @@ -360,7 +359,6 @@ export const AssetsFilter = ({ }, [ previousIsOpen, isOpen, - onApply, selectedConsumers, selectedEndpoints, selectedInsights, @@ -368,16 +366,16 @@ export const AssetsFilter = ({ selectedServices, setPersistedFilters, setGloballySelectedServices, - isServicesFilterEnabled + isServicesFilterEnabled, + setFilters ]); const handleClearFiltersButtonClick = () => { getData({ + ...query, services: isServicesFilterEnabled ? [] : selectedServices, insights: [], - operations: [], - assetScopeOption, - searchQuery + operations: [] }); }; @@ -412,22 +410,21 @@ export const AssetsFilter = ({ } getData({ + ...query, services, operations: [...endpoints, ...consumers, ...internals], - insights, - assetScopeOption, - searchQuery + insights }); }; - const servicesCategory = data?.data?.categories.find( + const servicesCategory = data?.categories.find( (x) => x.categoryName === "Services" ) ?? { categoryName: "Services", entries: [] }; - const operationsCategory = data?.data?.categories.find( + const operationsCategory = data?.categories.find( (x) => x.categoryName === "Operations" ); const endpointsCategory = operationsCategory?.categories?.find( @@ -449,7 +446,7 @@ export const AssetsFilter = ({ entries: [] }; - const insightsCategory = data?.data?.categories.find( + const insightsCategory = data?.categories.find( (x) => x.categoryName === "Insights" ) ?? { categoryName: "Insights", @@ -535,7 +532,7 @@ export const AssetsFilter = ({ void; - assetScopeOption: AssetScopeOption | null; - searchQuery: string; -} +import { InsightType } from "../../Insights/types"; export interface AssetFilterEntry { enabled: boolean; @@ -35,3 +28,12 @@ export interface AssetFilterQuery { export interface GetAssetFiltersDataPayload { query: AssetFilterQuery; } + +export interface GetAssetFiltersDataParams { + services: string[]; + operations: string[]; + insights: InsightType[]; + viewMode?: string; + scopeSpanCodeObjectId?: string; + searchQuery: string; +} diff --git a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx index 2424e38e6..5ac7d8448 100644 --- a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx +++ b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx @@ -18,26 +18,6 @@ type Story = StoryObj; export const Default: Story = { args: { - currentScope: { - span: { - displayName: "displayName", - spanCodeObjectId: "spanCodeObjectId", - serviceName: null, - role: "Entry", - methodId: null - }, - code: { - relatedCodeDetailsList: [], - codeDetailsList: [] - }, - hasErrors: false, - issuesInsightsCount: 0, - analyticsInsightsCount: 0, - unreadInsightsCount: 0 - }, - assetsCount: 1, - onAssetViewChange: () => { - return undefined; - } + assetsCount: 1 } }; diff --git a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx index 5bf7719a5..3d4e932b2 100644 --- a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx +++ b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx @@ -1,4 +1,7 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; +import { useAssetsSelector } from "../../../store/assets/useAssetsSelector"; +import { useConfigSelector } from "../../../store/config/useConfigSelector"; +import { useStore } from "../../../store/useStore"; import { isNumber } from "../../../typeGuards/isNumber"; import { formatUnit } from "../../../utils/formatUnit"; import { ArrowIcon } from "../../common/icons/12px/ArrowIcon"; @@ -6,37 +9,23 @@ 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"; - -type ViewMode = "descendants" | "children"; +import { AssetsViewScopeConfigurationProps, ViewMode } from "./types"; export const AssetsViewScopeConfiguration = ({ - currentScope, - onAssetViewChange, assetsCount }: AssetsViewScopeConfigurationProps) => { - const [viewMode, setViewMode] = useState("descendants"); + const { scope } = useConfigSelector(); + const { viewMode } = useAssetsSelector(); + const { setAssetsViewMode } = useStore.getState(); useEffect(() => { - const isEntryPoint = !currentScope || currentScope.span?.role === "Entry"; - - setViewMode(isEntryPoint ? "descendants" : "children"); + const isEntryPoint = !scope || scope.span?.role === "Entry"; - onAssetViewChange({ - scopedSpanCodeObjectId: currentScope?.span?.spanCodeObjectId, - isDirect: !isEntryPoint - }); - }, [currentScope, onAssetViewChange]); + setAssetsViewMode(isEntryPoint ? "descendants" : "children"); + }, [scope, setAssetsViewMode]); const handleToggleOptionChange = (value: ViewMode) => { - setViewMode(value); - - if (onAssetViewChange) { - onAssetViewChange({ - isDirect: value === "children", - scopedSpanCodeObjectId: currentScope?.span?.spanCodeObjectId - }); - } + setAssetsViewMode(value); }; const toggleOptions: ToggleOption[] = [ diff --git a/src/components/Assets/AssetsViewScopeConfiguration/types.ts b/src/components/Assets/AssetsViewScopeConfiguration/types.ts index da43b8398..8963799f8 100644 --- a/src/components/Assets/AssetsViewScopeConfiguration/types.ts +++ b/src/components/Assets/AssetsViewScopeConfiguration/types.ts @@ -1,12 +1,5 @@ -import { Scope } from "../../common/App/types"; - -export interface AssetsViewConfigurationProps { - onAssetViewChange: (assetViewScope: AssetScopeOption) => void; - currentScope: Scope; +export interface AssetsViewScopeConfigurationProps { assetsCount?: number; } -export interface AssetScopeOption { - scopedSpanCodeObjectId?: string; - isDirect: boolean; -} +export type ViewMode = "descendants" | "children"; diff --git a/src/components/Assets/index.tsx b/src/components/Assets/index.tsx index 21f715ac3..d935101f2 100644 --- a/src/components/Assets/index.tsx +++ b/src/components/Assets/index.tsx @@ -1,10 +1,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { useParams } from "react-router-dom"; -import { getFeatureFlagValue } from "../../featureFlags"; import { useDebounce } from "../../hooks/useDebounce"; import { usePrevious } from "../../hooks/usePrevious"; +import { useAssetsSelector } from "../../store/assets/useAssetsSelector"; import { useConfigSelector } from "../../store/config/useConfigSelector"; -import { FeatureFlag } from "../../types"; +import { useStore } from "../../store/useStore"; import { sendUserActionTrackingEvent } from "../../utils/actions/sendUserActionTrackingEvent"; import { useHistory } from "../Main/useHistory"; import { EmptyState } from "../common/EmptyState"; @@ -14,49 +14,45 @@ import { Tooltip } from "../common/v3/Tooltip"; import { AssetList } from "./AssetList"; import { AssetTypeList } from "./AssetTypeList"; import { AssetsFilter } from "./AssetsFilter"; -import { AssetFilterQuery } from "./AssetsFilter/types"; import { AssetsViewScopeConfiguration } from "./AssetsViewScopeConfiguration"; -import { AssetScopeOption } from "./AssetsViewScopeConfiguration/types"; import { NoDataMessage } from "./NoDataMessage"; import * as s from "./styles"; import { trackingEvents } from "./tracking"; import { DataRefresher } from "./types"; +const SEARCH_INPUT_DEBOUNCE_DELAY = 1000; // in milliseconds + export const Assets = () => { const [assetsCount, setAssetsCount] = useState(); const params = useParams(); const selectedAssetTypeId = useMemo(() => params.typeId, [params]); - const [searchInputValue, setSearchInputValue] = useState(""); - const debouncedSearchInputValue = useDebounce(searchInputValue, 1000); - const [assetScopeOption, setAssetScopeOption] = - useState(null); - const [selectedFilters, setSelectedFilters] = useState(); - const { scope, environments, backendInfo } = useConfigSelector(); - const previousScopeSpanCodeObjectId = usePrevious( - scope?.span?.spanCodeObjectId + const { search, filters } = useAssetsSelector(); + const { setAssetsSearch: setSearch } = useStore.getState(); + const [searchInputValue, setSearchInputValue] = useState(search); + const debouncedSearchInputValue = useDebounce( + searchInputValue, + SEARCH_INPUT_DEBOUNCE_DELAY ); + const { scope, environments } = useConfigSelector(); + const scopeSpanCodeObjectId = scope?.span?.spanCodeObjectId; + const previousScopeSpanCodeObjectId = usePrevious(scopeSpanCodeObjectId); const [assetTypeListDataRefresher, setAssetTypeListRefresher] = useState(null); const [assetListDataRefresher, setAssetListRefresher] = useState(null); const { goTo } = useHistory(); const isBackendUpgradeMessageVisible = false; - const areExtendedAssetsFiltersEnabled = getFeatureFlagValue( - backendInfo, - FeatureFlag.ARE_EXTENDED_ASSETS_FILTERS_ENABLED - ); + const { showAssetsHeaderToolBox } = useAssetsSelector(); useEffect(() => { - if (!scope?.span) { - setAssetScopeOption(null); + if (previousScopeSpanCodeObjectId !== scopeSpanCodeObjectId) { + setSearchInputValue(""); } - }, [scope]); + }, [scopeSpanCodeObjectId, previousScopeSpanCodeObjectId, setSearch]); useEffect(() => { - if (previousScopeSpanCodeObjectId !== scope?.span?.spanCodeObjectId) { - setSearchInputValue(""); - } - }, [scope?.span?.spanCodeObjectId, previousScopeSpanCodeObjectId]); + setSearch(debouncedSearchInputValue); + }, [debouncedSearchInputValue, setSearch]); const handleGoToAllAssets = () => { goTo(".."); @@ -70,10 +66,6 @@ export const Assets = () => { goTo(assetTypeId); }; - const handleApplyFilters = (filters: AssetFilterQuery) => { - setSelectedFilters(filters); - }; - const handleRefresh = () => { sendUserActionTrackingEvent(trackingEvents.REFRESH_BUTTON_CLICKED, { view: !selectedAssetTypeId ? "asset categories" : "assets" @@ -92,10 +84,6 @@ export const Assets = () => { setAssetsCount(count); }, []); - const handleAssetViewModeChange = useCallback((val: AssetScopeOption) => { - setAssetScopeOption(val); - }, []); - const handleAssetTypeListRefresherChange = useCallback( (refresher: () => void) => { setAssetTypeListRefresher({ refresh: refresher }); @@ -131,7 +119,7 @@ export const Assets = () => { return ; } - if (!selectedFilters) { + if (!filters && showAssetsHeaderToolBox) { return ; } @@ -139,9 +127,6 @@ export const Assets = () => { return ( @@ -152,9 +137,6 @@ export const Assets = () => { @@ -166,40 +148,32 @@ export const Assets = () => { {scope?.span && ( - + )} - - - - - - - - {scope?.span && ( - Assets filtered to current scope + {showAssetsHeaderToolBox && ( + <> + + + + + + + + {scope?.span && ( + Assets filtered to current scope + )} + )} + {renderContent()} ); diff --git a/src/components/Assets/utils.tsx b/src/components/Assets/utils.tsx index 7ce977ca0..163b06ecd 100644 --- a/src/components/Assets/utils.tsx +++ b/src/components/Assets/utils.tsx @@ -49,15 +49,14 @@ export const getAssetTypeInfo = ( }; export const checkIfAnyFiltersApplied = ( - filters: AssetFilterQuery | undefined, + filters: AssetFilterQuery, searchQuery: string, isServicesFilterEnabled: boolean ) => Boolean( - filters && - [ - ...filters.insights, - ...filters.operations, - ...(isServicesFilterEnabled ? filters.services : []) - ].length > 0 + [ + ...filters.insights, + ...filters.operations, + ...(isServicesFilterEnabled ? filters.services : []) + ].length > 0 ) || searchQuery.length > 0; diff --git a/src/components/Dashboard/Report/Cards/DiscoveredAssets/index.tsx b/src/components/Dashboard/Report/Cards/DiscoveredAssets/index.tsx index b11d6981c..b8d62a1db 100644 --- a/src/components/Dashboard/Report/Cards/DiscoveredAssets/index.tsx +++ b/src/components/Dashboard/Report/Cards/DiscoveredAssets/index.tsx @@ -19,7 +19,7 @@ export const DiscoveredAssets = ({ return ( ); diff --git a/src/components/Dashboard/Report/Cards/DiscoveredIssues/index.tsx b/src/components/Dashboard/Report/Cards/DiscoveredIssues/index.tsx index 73c92ca18..63965d806 100644 --- a/src/components/Dashboard/Report/Cards/DiscoveredIssues/index.tsx +++ b/src/components/Dashboard/Report/Cards/DiscoveredIssues/index.tsx @@ -12,7 +12,7 @@ export const DiscoveredIssues = ({ }: DiscoveredIssuesProps) => { return ( { {/* */} diff --git a/src/components/Errors/ErrorDetails/styles.ts b/src/components/Errors/ErrorDetails/styles.ts index b1760c7c6..e6fad6588 100644 --- a/src/components/Errors/ErrorDetails/styles.ts +++ b/src/components/Errors/ErrorDetails/styles.ts @@ -1,6 +1,9 @@ import styled from "styled-components"; import { Card } from "../../common/v3/Card"; -import { Content as CardContent } from "../../common/v3/Card/styles"; +import { + Content as CardContent, + Header as CardHeader +} from "../../common/v3/Card/styles"; export const Container = styled.div` padding: 8px; @@ -15,11 +18,11 @@ export const ErrorDetailsCard = styled(Card)` min-height: 450px; overflow: hidden; - & > ${CardContent}:first-child { + & > ${CardHeader} { height: initial; } - & > ${CardContent}:last-child { + & > ${CardContent} { overflow: hidden; } `; diff --git a/src/components/Insights/InsightsCatalog/FilterPanel/index.tsx b/src/components/Insights/InsightsCatalog/FilterPanel/index.tsx index b0d9f92f9..71bc16da2 100644 --- a/src/components/Insights/InsightsCatalog/FilterPanel/index.tsx +++ b/src/components/Insights/InsightsCatalog/FilterPanel/index.tsx @@ -12,7 +12,7 @@ export const FilterPanel = ({ unreadCount }: FilterPanelProps) => { const { filters } = useInsightsSelector(); - const { setFilters } = useStore.getState(); + const { setInsightsFilters: setFilters } = useStore.getState(); const handleFilterChipClick = (selectedFilter?: InsightFilterType) => { const newFilters = new Set(filters); diff --git a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx index 05c4b51d6..770a6ff2a 100644 --- a/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx +++ b/src/components/Insights/InsightsCatalog/InsightsPage/AffectedEndpointsSelector/index.tsx @@ -60,8 +60,8 @@ export const AffectedEndpointsSelector = ({ listHeader={ 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 ( } {insight.status && statusInfo && ( {statusInfo.label}} + title={{statusInfo.label}} placement={"top"} fullWidth={true} > diff --git a/src/components/Insights/InsightsCatalog/index.tsx b/src/components/Insights/InsightsCatalog/index.tsx index 0a2853fd3..f2a70effc 100644 --- a/src/components/Insights/InsightsCatalog/index.tsx +++ b/src/components/Insights/InsightsCatalog/index.tsx @@ -67,10 +67,10 @@ export const InsightsCatalog = ({ onRefresh }: InsightsCatalogProps) => { const { - setViewMode: setMode, - setPage, - setSorting, - setSearch + setInsightsViewMode: setMode, + setInsightsPage: setPage, + setInsightsSorting: setSorting, + setInsightsSearch: setSearch } = useStore.getState(); const { diff --git a/src/components/Insights/Issues/IssuesFilter/index.tsx b/src/components/Insights/Issues/IssuesFilter/index.tsx index f1d30e79c..69da1203e 100644 --- a/src/components/Insights/Issues/IssuesFilter/index.tsx +++ b/src/components/Insights/Issues/IssuesFilter/index.tsx @@ -22,8 +22,11 @@ import { trackingEvents } from "./tracking"; export const IssuesFilter = () => { const { filteredInsightTypes, filters } = useInsightsSelector(); const { selectedServices, backendInfo, scope } = useConfigSelector(); - const { setSelectedServices, setFilteredInsightTypes, setFilters } = - useStore.getState(); + const { + setSelectedServices, + setInsightsFilteredInsightTypes: setFilteredInsightTypes, + setInsightsFilters: setFilters + } = useStore.getState(); const isCriticalOnly = useMemo( () => filters.includes("criticality"), [filters] @@ -259,7 +262,7 @@ export const IssuesFilter = () => { diff --git a/src/components/Insights/Issues/useIssuesFilters.ts b/src/components/Insights/Issues/useIssuesFilters.ts index 7bd22ac2f..5b174314a 100644 --- a/src/components/Insights/Issues/useIssuesFilters.ts +++ b/src/components/Insights/Issues/useIssuesFilters.ts @@ -30,7 +30,7 @@ export const useIssuesFilters = () => { viewMode, filters } = useInsightsSelector(); - const { setIssuesFilters: setData } = useStore.getState(); + const { setInsightsIssuesFilters: setData } = useStore.getState(); const [lastSetDataTimeStamp, setLastSetDataTimeStamp] = useState(); const previousLastSetDataTimeStamp = usePrevious(lastSetDataTimeStamp); const refreshTimerId = useRef(); diff --git a/src/components/Insights/index.tsx b/src/components/Insights/index.tsx index 17dbb541c..eeb11aaf4 100644 --- a/src/components/Insights/index.tsx +++ b/src/components/Insights/index.tsx @@ -242,9 +242,9 @@ export const Insights = ({ insightViewType }: InsightsProps) => { isRegistrationEnabled && !userRegistrationEmail; const { setInsightViewType, - setFilteredInsightTypes, - setFilters, - insightsReset: reset + setInsightsFilteredInsightTypes: setFilteredInsightTypes, + setInsightsFilters: setFilters, + resetInsights: reset } = useStore.getState(); const { insightViewType: storedInsightViewType, diff --git a/src/components/Insights/useInsightsData.ts b/src/components/Insights/useInsightsData.ts index fd76a1668..6ece7b824 100644 --- a/src/components/Insights/useInsightsData.ts +++ b/src/components/Insights/useInsightsData.ts @@ -145,7 +145,8 @@ export const useInsightsData = ({ isDataLoading: isLoading, insightViewType } = useInsightsSelector(); - const { setData, setIsDataLoading: setIsLoading } = useStore.getState(); + const { setInsightsData: setData, setIsInsightsDataLoading: setIsLoading } = + useStore.getState(); const isInitialLoading = !data && isLoading; const [lastSetDataTimeStamp, setLastSetDataTimeStamp] = useState(); const previousLastSetDataTimeStamp = usePrevious(lastSetDataTimeStamp); diff --git a/src/components/Main/index.tsx b/src/components/Main/index.tsx index 4e581aeec..c60836c57 100644 --- a/src/components/Main/index.tsx +++ b/src/components/Main/index.tsx @@ -50,8 +50,15 @@ const getURLToNavigateOnCodeLensClick = (scope: Scope): string | undefined => { export const Main = () => { const location = useLocation(); - const { environments, environment, scope, userInfo, backendInfo } = - useConfigSelector(); + const { + environments, + environment, + scope, + userInfo, + backendInfo, + selectedServices + } = useConfigSelector(); + const { setSelectedServices } = useStore.getState(); const previousEnvironment = usePrevious(environment); const userId = userInfo?.id; const previousUserId = usePrevious(userId); @@ -63,8 +70,6 @@ export const Main = () => { "project" ); const previousPersistedServices = usePrevious(persistedServices); - const selectedServices = useConfigSelector().selectedServices; - const { setSelectedServices } = useStore.getState(); const isInitialized = useMemo( () => !isUndefined(persistedServices), [persistedServices] @@ -205,6 +210,9 @@ export const Main = () => { } 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 +220,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/tracking.ts b/src/components/Main/tracking.ts index 9b3e3712a..bad164eef 100644 --- a/src/components/Main/tracking.ts +++ b/src/components/Main/tracking.ts @@ -17,7 +17,10 @@ export const trackingEvents = addPrefix( "promotion registration close button clicked", PROMOTION_DISCARDED: "promotion discarded", PROMOTION_REGISTRATION_FORM_OPENED: "promotion registration form opened", - LOGIN_SCREEN_VIEWED: "login screen viewed" + LOGIN_SCREEN_VIEWED: "login screen viewed", + SCOPE_BAR_EXPAND_BUTTON_CLICKED: "span info expand button clicked", + SCOPE_BAR_COLLAPSE_BUTTON_CLICKED: "scope bar collapse button clicked", + SPAN_INFO_COLLAPSE_BUTTON_CLICKED: "span info collapse button clicked" }, " " ); 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/Navigation/KebabMenu/index.tsx b/src/components/Navigation/KebabMenu/index.tsx index 8cc56800d..36a301020 100644 --- a/src/components/Navigation/KebabMenu/index.tsx +++ b/src/components/Navigation/KebabMenu/index.tsx @@ -56,6 +56,7 @@ export const KebabMenu = ({ onClose }: KebabMenuProps) => { const handleOpenDocsClick = () => { sendUserActionTrackingEvent(trackingEvents.OPEN_DOCS_CLICKED); openURLInDefaultBrowser(DIGMA_DOCUMENTATION); + onClose(); }; const handleDashboardClick = () => { @@ -68,6 +69,7 @@ export const KebabMenu = ({ onClose }: KebabMenuProps) => { } }); } + onClose(); }; // const handleReportClick = () => { diff --git a/src/components/Navigation/ScopeBar/index.tsx b/src/components/Navigation/ScopeBar/index.tsx index b5e275b1c..489a09f84 100644 --- a/src/components/Navigation/ScopeBar/index.tsx +++ b/src/components/Navigation/ScopeBar/index.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { history } from "../../../containers/Main/history"; import { isString } from "../../../typeGuards/isString"; import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; +import { trackingEvents as mainTrackingEvents } from "../../Main/tracking"; import { CodeDetails, Scope } from "../../common/App/types"; import { NewPopover } from "../../common/NewPopover"; import { CrosshairIcon } from "../../common/icons/16px/CrosshairIcon"; @@ -108,6 +109,15 @@ export const ScopeBar = ({ }; const handleExpandCollapseButtonClick = () => { + if (isExpanded) { + sendUserActionTrackingEvent( + mainTrackingEvents.SCOPE_BAR_COLLAPSE_BUTTON_CLICKED + ); + } else { + sendUserActionTrackingEvent( + mainTrackingEvents.SCOPE_BAR_EXPAND_BUTTON_CLICKED + ); + } onExpandCollapseChange(!isExpanded); }; diff --git a/src/components/Navigation/SpanInfo/index.tsx b/src/components/Navigation/SpanInfo/index.tsx index 2b079cc04..98b7f4be9 100644 --- a/src/components/Navigation/SpanInfo/index.tsx +++ b/src/components/Navigation/SpanInfo/index.tsx @@ -1,6 +1,8 @@ +import { sendUserActionTrackingEvent } from "../../../utils/actions/sendUserActionTrackingEvent"; import { GlobeIcon } from "../../common/icons/16px/GlobeIcon"; import { WrenchIcon } from "../../common/icons/16px/WrenchIcon"; import { Tooltip } from "../../common/v3/Tooltip"; +import { trackingEvents } from "../../Main/tracking"; import * as s from "./styles"; import { SpanInfoProps } from "./types"; @@ -18,6 +20,9 @@ const getLanguage = (assetTypeId: string) => { export const SpanInfo = ({ onCollapse, data }: SpanInfoProps) => { const handleCollapseButtonClick = () => { + sendUserActionTrackingEvent( + trackingEvents.SPAN_INFO_COLLAPSE_BUTTON_CLICKED + ); onCollapse(); }; diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 4e10e79d5..c6ae12cbd 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -487,7 +487,27 @@ export const App = ({ theme, children, id }: AppProps) => { handleSetRunConfiguration ); }; - }, []); + }, [ + setJaegerURL, + setIsJaegerEnabled, + setIsDigmaEngineInstalled, + setIsDigmaEngineRunning, + setDigmaStatus, + setIsDockerInstalled, + setIsDockerComposeInstalled, + setDigmaApiUrl, + setUserRegistrationEmail, + setIsObservabilityEnabled, + setBackendInfo, + setEnvironments, + setEnvironment, + setScope, + setUserInfo, + setInsightStats, + setRunConfig, + setIsDigmathonGameFinished, + setIsMicrometerProject + ]); const styledComponentsTheme = getStyledComponentsTheme( currentTheme, 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/assets/assetsSlice.ts b/src/store/assets/assetsSlice.ts new file mode 100644 index 000000000..8545a1f10 --- /dev/null +++ b/src/store/assets/assetsSlice.ts @@ -0,0 +1,84 @@ +import { createSlice } from "zustand-slices"; +import { + AssetsData, + Sorting, + SORTING_CRITERION, + SORTING_ORDER +} from "../../components/Assets/AssetList/types"; +import { AssetCategoriesData } from "../../components/Assets/AssetTypeList/types"; +import { AssetFilterQuery } from "../../components/Assets/AssetsFilter/types"; +import { ViewMode } from "../../components/Assets/AssetsViewScopeConfiguration/types"; + +export interface AssetsState { + assetCategoriesData: AssetCategoriesData | null; + isAssetCategoriesDataLoading: boolean; + selectedAssetCategory: string | null; + assets: AssetsData | null; + areAssetsLoading: boolean; + filters: AssetFilterQuery; + viewMode: ViewMode; + search: string; + page: number; + sorting: Sorting; + showAssetsHeaderToolBox: boolean; +} + +const allFiltersInitialState: { + filters: AssetFilterQuery; + viewMode: ViewMode; + search: string; + page: number; + sorting: Sorting; +} = { + filters: { + services: [], + operations: [], + insights: [] + }, + viewMode: "descendants", + search: "", + page: 0, + sorting: { + criterion: SORTING_CRITERION.CRITICAL_INSIGHTS, + order: SORTING_ORDER.DESC + } +}; + +export const initialState: AssetsState = { + ...allFiltersInitialState, + assetCategoriesData: null, + isAssetCategoriesDataLoading: false, + selectedAssetCategory: null, + assets: null, + areAssetsLoading: false, + showAssetsHeaderToolBox: true +}; + +const set = (update: Partial) => (state: AssetsState) => ({ + ...state, + ...update +}); + +export const assetsSlice = createSlice({ + name: "assets", + value: initialState, + actions: { + setAssetCategoriesData: (data: AssetCategoriesData) => + set({ assetCategoriesData: data }), + setIsAssetCategoriesDataLoading: (isLoading: boolean) => + set({ isAssetCategoriesDataLoading: isLoading }), + setSelectedAssetCategory: (category: string | null) => + set({ selectedAssetCategory: category }), + setAssets: (assets: AssetsData) => set({ assets }), + setAreAssetsLoading: (isLoading: boolean) => + set({ areAssetsLoading: isLoading }), + setAssetsFilters: (filters: AssetFilterQuery) => set({ filters }), + setAssetsViewMode: (viewMode: ViewMode) => set({ viewMode }), + setAssetsSearch: (search: string) => set({ search }), + setAssetsPage: (page: number) => set({ page }), + setAssetsSorting: (sorting: Sorting) => set({ sorting }), + setShowAssetsHeaderToolBox: (showAssetsHeaderToolBox: boolean) => + set({ showAssetsHeaderToolBox }), + resetAssets: () => set(initialState) + } +}); diff --git a/src/store/assets/useAssetsSelector.ts b/src/store/assets/useAssetsSelector.ts new file mode 100644 index 000000000..759e16d26 --- /dev/null +++ b/src/store/assets/useAssetsSelector.ts @@ -0,0 +1,3 @@ +import { useStore } from "../useStore"; + +export const useAssetsSelector = () => useStore((state) => state.assets); diff --git a/src/store/insights/insightsSlice.ts b/src/store/insights/insightsSlice.ts index 1394fda06..d8ed3c787 100644 --- a/src/store/insights/insightsSlice.ts +++ b/src/store/insights/insightsSlice.ts @@ -51,21 +51,22 @@ export const insightsSlice = createSlice({ name: "insights", value: initialState, actions: { - setData: (data: InsightsData) => set({ data }), - setIsDataLoading: (isDataLoading: boolean) => set({ isDataLoading }), - setSearch: (search: string) => set({ search }), - setPage: (page: number) => set({ page }), - setSorting: (sorting: Sorting) => set({ sorting }), - setViewMode: (viewMode: ViewMode) => set({ viewMode }), - setFilters: (filters: InsightFilterType[]) => set({ filters }), - setFilteredInsightTypes: (filteredInsightTypes: string[]) => + setInsightsData: (data: InsightsData) => set({ data }), + setIsInsightsDataLoading: (isDataLoading: boolean) => + set({ isDataLoading }), + setInsightsSearch: (search: string) => set({ search }), + setInsightsPage: (page: number) => set({ page }), + setInsightsSorting: (sorting: Sorting) => set({ sorting }), + setInsightsViewMode: (viewMode: ViewMode) => set({ viewMode }), + setInsightsFilters: (filters: InsightFilterType[]) => set({ filters }), + setInsightsFilteredInsightTypes: (filteredInsightTypes: string[]) => set({ filteredInsightTypes }), setInsightViewType: (insightViewType: InsightViewType) => set({ insightViewType }), - setIssuesFilters: (issuesFilters: IssuesFiltersData) => + setInsightsIssuesFilters: (issuesFilters: IssuesFiltersData) => set({ issuesFilters }), - setAreIssuesFiltersLoading: (areIssuesFiltersLoading: boolean) => + setAreInsightsIssuesFiltersLoading: (areIssuesFiltersLoading: boolean) => set({ areIssuesFiltersLoading }), - insightsReset: () => set(initialState) + resetInsights: () => set(initialState) } }); diff --git a/src/store/useStore.ts b/src/store/useStore.ts index 827bee2ba..d022aa7cc 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 "./assets/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 !==