diff --git a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx index 330a6d595..e750929bc 100644 --- a/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx +++ b/src/components/Assets/AssetsViewScopeConfiguration/AssetsViewScopeConfiguration.stories.tsx @@ -31,7 +31,8 @@ export const Default: Story = { }, hasErrors: false, issuesInsightsCount: 0, - analyticsInsightsCount: 0 + analyticsInsightsCount: 0, + unreadInsightsCount: 0 }, assetsCount: 1, onAssetViewChange: () => { diff --git a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx index 85b0634b8..c1853c0a5 100644 --- a/src/components/Assets/AssetsViewScopeConfiguration/index.tsx +++ b/src/components/Assets/AssetsViewScopeConfiguration/index.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import { isNumber } from "../../../typeGuards/isNumber"; +import { formatUnit } from "../../../utils/formatUnit"; import { ArrowIcon } from "../../common/icons/12px/ArrowIcon"; import { TreeNodesIcon } from "../../common/icons/12px/TreeNodesIcon"; import { Toggle } from "../../common/v3/Toggle"; @@ -64,7 +65,7 @@ export const AssetsViewScopeConfiguration = ({ {isNumber(assetsCount) ? ` ${assetsCount} ` : " "} - {assetTypeDescription} asset{assetsCount === 1 ? "" : "s"} + {assetTypeDescription} {formatUnit(assetsCount || 0, "asset")} ); diff --git a/src/components/Insights/DurationBreakdownInsight/index.tsx b/src/components/Insights/DurationBreakdownInsight/index.tsx index 2aa086425..03b9b30f3 100644 --- a/src/components/Insights/DurationBreakdownInsight/index.tsx +++ b/src/components/Insights/DurationBreakdownInsight/index.tsx @@ -40,6 +40,9 @@ const getDurationTitle = (breakdownEntry: SpanDurationBreakdownEntry) => { return {title}; }; +/** + * @deprecated + */ export const DurationBreakdownInsight = ( props: DurationBreakdownInsightProps ) => { diff --git a/src/components/Insights/DurationChange/index.tsx b/src/components/Insights/DurationChange/index.tsx index 092423acb..0f7f4fbf6 100644 --- a/src/components/Insights/DurationChange/index.tsx +++ b/src/components/Insights/DurationChange/index.tsx @@ -99,6 +99,9 @@ const renderArrowIcon = ( ); }; +/** + * @deprecated + */ export const DurationChange = (props: DurationChangeProps) => { const theme = useTheme(); diff --git a/src/components/Insights/DurationInsight/index.tsx b/src/components/Insights/DurationInsight/index.tsx index 3ac1ba9a3..dfa112674 100644 --- a/src/components/Insights/DurationInsight/index.tsx +++ b/src/components/Insights/DurationInsight/index.tsx @@ -132,6 +132,9 @@ const calculateBars = ( return newBars; }; +/** + * @deprecated + */ export const DurationInsight = (props: DurationInsightProps) => { // const config = useContext(ConfigContext); const theme = useTheme(); diff --git a/src/components/Insights/DurationSlowdownSourceInsight/index.tsx b/src/components/Insights/DurationSlowdownSourceInsight/index.tsx index e9ba2b76e..489e410e1 100644 --- a/src/components/Insights/DurationSlowdownSourceInsight/index.tsx +++ b/src/components/Insights/DurationSlowdownSourceInsight/index.tsx @@ -6,6 +6,9 @@ import { DurationSlowdownSource } from "../types"; import * as s from "./styles"; import { DurationSlowdownSourceInsightProps } from "./types"; +/** + * @deprecated + */ export const DurationSlowdownSourceInsight = ( props: DurationSlowdownSourceInsightProps ) => { diff --git a/src/components/Insights/EndpointNPlusOneInsight/index.tsx b/src/components/Insights/EndpointNPlusOneInsight/index.tsx index 4c4d1c8f7..f81140697 100644 --- a/src/components/Insights/EndpointNPlusOneInsight/index.tsx +++ b/src/components/Insights/EndpointNPlusOneInsight/index.tsx @@ -21,6 +21,9 @@ import { EndpointNPlusOneInsightProps } from "./types"; const FRACTION_MIN_LIMIT = 0.01; const PAGE_SIZE = 3; +/** + * @deprecated + */ export const EndpointNPlusOneInsight = ( props: EndpointNPlusOneInsightProps ) => { diff --git a/src/components/Insights/EndpointQueryOptimizationInsight/index.tsx b/src/components/Insights/EndpointQueryOptimizationInsight/index.tsx index 5861fbf02..796f59a67 100644 --- a/src/components/Insights/EndpointQueryOptimizationInsight/index.tsx +++ b/src/components/Insights/EndpointQueryOptimizationInsight/index.tsx @@ -19,6 +19,9 @@ import { EndpointQueryOptimizationInsightProps } from "./types"; const PAGE_SIZE = 3; +/** + * @deprecated + */ export const EndpointQueryOptimizationInsight = ( props: EndpointQueryOptimizationInsightProps ) => { diff --git a/src/components/Insights/ErrorsInsight/index.tsx b/src/components/Insights/ErrorsInsight/index.tsx index a1399acb4..91f9296f9 100644 --- a/src/components/Insights/ErrorsInsight/index.tsx +++ b/src/components/Insights/ErrorsInsight/index.tsx @@ -4,6 +4,9 @@ import { Description, Link } from "../styles"; import * as s from "./styles"; import { ErrorsInsightProps } from "./types"; +/** + * @deprecated + */ export const ErrorsInsight = (props: ErrorsInsightProps) => { const handleErrorLinkClick = (errorId: string) => { props.onErrorSelect(errorId, props.insight.type); diff --git a/src/components/Insights/ExcessiveAPICallsInsight/index.tsx b/src/components/Insights/ExcessiveAPICallsInsight/index.tsx index 45e05ddfe..e26d2aa8f 100644 --- a/src/components/Insights/ExcessiveAPICallsInsight/index.tsx +++ b/src/components/Insights/ExcessiveAPICallsInsight/index.tsx @@ -14,6 +14,9 @@ import { ExcessiveAPICallsInsightProps } from "./types"; const PAGE_SIZE = 3; +/** + * @deprecated + */ export const ExcessiveAPICallsInsight = ( props: ExcessiveAPICallsInsightProps ) => { diff --git a/src/components/Insights/HighNumberOfQueriesInsight/index.tsx b/src/components/Insights/HighNumberOfQueriesInsight/index.tsx index 88b8e5b32..112ecb782 100644 --- a/src/components/Insights/HighNumberOfQueriesInsight/index.tsx +++ b/src/components/Insights/HighNumberOfQueriesInsight/index.tsx @@ -14,6 +14,9 @@ import { Trace } from "../types"; import * as s from "./styles"; import { HighNumberOfQueriesInsightProps } from "./types"; +/** + * @deprecated + */ export const HighNumberOfQueriesInsight = ( props: HighNumberOfQueriesInsightProps ) => { diff --git a/src/components/Insights/InsightCard/index.tsx b/src/components/Insights/InsightCard/index.tsx index 0e175a20e..44c67eb67 100644 --- a/src/components/Insights/InsightCard/index.tsx +++ b/src/components/Insights/InsightCard/index.tsx @@ -31,6 +31,9 @@ const RECALCULATE = "recalculate"; const DEFAULT_PERCENTILE = 0.5; const IS_NEW_TIME_LIMIT = 1000 * 60 * 10; // in milliseconds +/** + * @deprecated + */ export const InsightCard = (props: InsightCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const [isKebabMenuOpen, setIsKebabMenuOpen] = useState(false); diff --git a/src/components/Insights/InsightList/index.tsx b/src/components/Insights/InsightList/index.tsx index 702d3d382..6f4a20b2d 100644 --- a/src/components/Insights/InsightList/index.tsx +++ b/src/components/Insights/InsightList/index.tsx @@ -23,6 +23,7 @@ import { ErrorsInsight } from "../ErrorsInsight"; import { ExcessiveAPICallsInsight } from "../ExcessiveAPICallsInsight"; import { HighNumberOfQueriesInsight } from "../HighNumberOfQueriesInsight"; import { InsightCard } from "../InsightCard"; +import { ViewMode } from "../InsightsCatalog/types"; import { NPlusOneInsight } from "../NPlusOneInsight"; import { NoObservabilityCard } from "../NoObservabilityCard"; import { NoScalingIssueInsight } from "../NoScalingIssueInsight"; @@ -233,8 +234,11 @@ const renderInsightCard = ( spanCodeObjectId: string | undefined, event?: string ) => void, - isJiraHintEnabled: boolean + isJiraHintEnabled: boolean, + viewMode: ViewMode ): JSX.Element | undefined => { + const isMarkAsReadButtonEnabled = viewMode === ViewMode.OnlyUnread; + const handleErrorSelect = (errorId: string, insightType: InsightType) => { sendTrackingEvent(globalTrackingEvents.USER_ACTION, { action: `Follow ${insightType} link` @@ -362,6 +366,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -374,6 +379,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -387,6 +393,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -401,6 +408,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -415,6 +423,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -426,6 +435,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -441,6 +451,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -454,6 +465,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -469,6 +481,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -484,6 +497,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -500,6 +514,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -527,6 +542,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -539,6 +555,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -552,6 +569,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -565,6 +583,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -579,6 +598,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -593,6 +613,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -608,6 +629,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -620,6 +642,7 @@ const renderInsightCard = ( onRecalculate={handleRecalculate} onRefresh={handleRefresh} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -636,6 +659,7 @@ const renderInsightCard = ( onJiraTicketCreate={onJiraTicketCreate} isJiraHintEnabled={isJiraHintEnabled} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -651,6 +675,7 @@ const renderInsightCard = ( onRefresh={handleRefresh} onJiraTicketCreate={onJiraTicketCreate} onGoToSpan={handleGoToSpan} + isMarkAsReadButtonEnabled={isMarkAsReadButtonEnabled} /> ); } @@ -659,6 +684,9 @@ const renderInsightCard = ( const IS_INSIGHT_JIRA_TICKET_HINT_SHOWN_PERSISTENCE_KEY = "isInsightJiraTicketHintShown"; +/** + * @deprecated + */ export const InsightList = (props: InsightListProps) => { const [insightGroups, setInsightGroups] = useState([]); const [isAutofixing, setIsAutofixing] = useState(false); @@ -748,7 +776,8 @@ export const InsightList = (props: InsightListProps) => { return renderInsightCard( insight, handleShowJiraTicket, - isJiraHintEnabled + isJiraHintEnabled, + props.viewMode ); }) ) : ( diff --git a/src/components/Insights/InsightList/types.ts b/src/components/Insights/InsightList/types.ts index 32fb84273..839604249 100644 --- a/src/components/Insights/InsightList/types.ts +++ b/src/components/Insights/InsightList/types.ts @@ -1,3 +1,4 @@ +import { ViewMode } from "../InsightsCatalog/types"; import { GenericCodeObjectInsight, MethodSpan } from "../types"; export interface InsightListProps { @@ -13,6 +14,7 @@ export interface InsightListProps { insight: GenericCodeObjectInsight, spanCodeObjectId?: string ) => void; + viewMode: ViewMode; } export interface isInsightJiraTicketHintShownPayload { diff --git a/src/components/Insights/InsightsCatalog/InsightsCatalog.stories.tsx b/src/components/Insights/InsightsCatalog/InsightsCatalog.stories.tsx new file mode 100644 index 000000000..3ea49e5f6 --- /dev/null +++ b/src/components/Insights/InsightsCatalog/InsightsCatalog.stories.tsx @@ -0,0 +1,41 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { InsightsCatalog } from "."; +import { SORTING_ORDER } from "../../common/SortingSelector/types"; +import { mockedSpanBottleneckInsight } from "../common/insights/EndpointBottleneckInsight/mockData"; +import { SORTING_CRITERION } from "./types"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/InsightsCatalog", + component: InsightsCatalog, + 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: { + insights: [{ ...mockedSpanBottleneckInsight, isRead: false }], + totalCount: 1, + dismissedCount: 1, + defaultQuery: { + page: 0, + sorting: { + criterion: SORTING_CRITERION.LATEST, + order: SORTING_ORDER.DESC + }, + searchQuery: null, + showDismissed: false, + insightViewType: "Issues", + showUnreadOnly: false + }, + isDismissalEnabled: true, + unreadCount: 1, + isMarkingAsReadEnabled: true + } +}; diff --git a/src/components/Insights/InsightsCatalog/index.tsx b/src/components/Insights/InsightsCatalog/index.tsx index 32fce607e..116f2e534 100644 --- a/src/components/Insights/InsightsCatalog/index.tsx +++ b/src/components/Insights/InsightsCatalog/index.tsx @@ -2,10 +2,14 @@ import { useCallback, useContext, useEffect, useState } from "react"; import { usePrevious } from "../../../hooks/usePrevious"; import { useTheme } from "styled-components"; +import { actions as globalActions } from "../../../actions"; import { useDebounce } from "../../../hooks/useDebounce"; import { isNumber } from "../../../typeGuards/isNumber"; +import { isString } from "../../../typeGuards/isString"; import { isUndefined } from "../../../typeGuards/isUndefined"; +import { formatUnit } from "../../../utils/formatUnit"; import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; +import { ChangeScopePayload } from "../../Navigation/types"; import { ConfigContext } from "../../common/App/ConfigContext"; import { Pagination } from "../../common/Pagination"; import { SearchInput } from "../../common/SearchInput"; @@ -20,13 +24,10 @@ import { Tooltip } from "../../common/v3/Tooltip"; import { InsightsPage } from "../InsightsPage"; import { trackingEvents } from "../tracking"; import * as s from "./styles"; -import { InsightsCatalogProps, SORTING_CRITERION } from "./types"; +import { InsightsCatalogProps, SORTING_CRITERION, ViewMode } from "./types"; +import { useMarkingAllAsRead } from "./useMarkingAllAsRead"; const PAGE_SIZE = 10; -enum ViewMode { - All, - OnlyDismissed -} export const InsightsCatalog = (props: InsightsCatalogProps) => { const { insights, onJiraTicketCreate, defaultQuery, totalCount } = props; @@ -50,11 +51,22 @@ export const InsightsCatalog = (props: InsightsCatalogProps) => { const [mode, setMode] = useState(ViewMode.All); const previousMode = usePrevious(mode); const theme = useTheme(); + const { isMarkingAllAsReadInProgress, markAllAsRead } = useMarkingAllAsRead( + config.scope?.span || null + ); + const previousIsMarkingAllAsReadInProgress = usePrevious( + isMarkingAllAsReadInProgress + ); - const isViewModeButtonVisible = + const isDismissalViewModeButtonVisible = props.isDismissalEnabled && (isUndefined(props.dismissedCount) || props.dismissedCount > 0); // isUndefined - check for backward compatibility, always show when BE does not return this counter + const isMarkingAsReadToolbarVisible = + props.isMarkingAsReadEnabled && + isNumber(props.unreadCount) && + props.unreadCount > 0; + const refreshData = useCallback( () => props.onQueryChange({ @@ -62,7 +74,8 @@ export const InsightsCatalog = (props: InsightsCatalogProps) => { page, sorting, searchQuery: debouncedSearchInputValue, - showDismissed: mode === ViewMode.OnlyDismissed + showDismissed: mode === ViewMode.OnlyDismissed, + showUnreadOnly: mode === ViewMode.OnlyUnread }), [ page, @@ -81,12 +94,48 @@ export const InsightsCatalog = (props: InsightsCatalogProps) => { refreshData(); }; - const handleViewModeChange = () => { + + const handleDismissalViewModeButtonClick = () => { const newMode = mode === ViewMode.All ? ViewMode.OnlyDismissed : ViewMode.All; setMode(newMode); }; + const handleUnreadOnlyLinkClick = () => { + setMode(ViewMode.OnlyUnread); + }; + + const handleReadAllLinkClick = () => { + markAllAsRead(); + }; + + const handleBackToAllInsightsButtonClick = () => { + setMode(ViewMode.All); + }; + + useEffect(() => { + if (previousIsMarkingAllAsReadInProgress && !isMarkingAllAsReadInProgress) { + refreshData(); + + // Trigger SET_SCOPE response message from the plugin with actual unread insights count + window.sendMessageToDigma({ + action: globalActions.CHANGE_SCOPE, + payload: { + span: config.scope?.span + ? { + spanCodeObjectId: config.scope.span.spanCodeObjectId + } + : null + } + }); + } + }, [ + isMarkingAllAsReadInProgress, + previousIsMarkingAllAsReadInProgress, + refreshData, + config.scope + ]); + useEffect(() => { if (!previousScope || previousScope !== config.scope?.span) { setSearchInputValue(""); @@ -108,11 +157,16 @@ export const InsightsCatalog = (props: InsightsCatalogProps) => { refreshData(); }, []); + useEffect(() => { + setPage(0); + }, [mode]); + useEffect(() => { if ( (isNumber(previousPage) && previousPage !== page) || (previousSorting && previousSorting !== sorting) || - previousSearchQuery !== debouncedSearchInputValue || + (isString(previousSearchQuery) && + previousSearchQuery !== debouncedSearchInputValue) || previousMode !== mode ) { refreshData(); @@ -132,59 +186,96 @@ export const InsightsCatalog = (props: InsightsCatalogProps) => { return ( <> - { - setSearchInputValue(val); - }} - value={searchInputValue} - /> - { - setSorting(val); - }} - options={[ - ...(defaultQuery.insightViewType === "Issues" - ? [ - { - value: SORTING_CRITERION.CRITICAL_INSIGHTS, - label: "Critical issues", - defaultOrder: SORTING_ORDER.DESC - } - ] - : []), - { - value: SORTING_CRITERION.LATEST, - label: "Latest", - defaultOrder: SORTING_ORDER.DESC - } - ]} - default={defaultQuery.sorting} - /> - - + { + setSearchInputValue(val); + }} + value={searchInputValue} /> - - - {mode === ViewMode.OnlyDismissed && ( - -