diff --git a/src/components/Insights/common/InsightCard/InsightCard.stories.tsx b/src/components/Insights/common/InsightCard/InsightCard.stories.tsx
index 9483ed449..0c4f0c930 100644
--- a/src/components/Insights/common/InsightCard/InsightCard.stories.tsx
+++ b/src/components/Insights/common/InsightCard/InsightCard.stories.tsx
@@ -1,5 +1,7 @@
import { Meta, StoryObj } from "@storybook/react";
import { InsightCard } from ".";
+import { ConfigContext, InitialData } from "../../../common/App/ConfigContext";
+import { DeploymentType } from "../../../common/App/types";
import { mockedEndpointNPlusOneInsight } from "../../EndpointNPlusOneInsight/mockData";
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
@@ -69,3 +71,25 @@ export const Dismissed: Story = {
onOpenHistogram: undefined
}
};
+
+export const WithNewVersion: Story = {
+ decorators: [
+ (Story) => (
+
+
+
+ )
+ ],
+ args: {
+ isAsync: true,
+ insight: { ...mockedEndpointNPlusOneInsight, criticality: 0.9 }
+ }
+};
diff --git a/src/components/Insights/common/InsightCard/InsightHeader/index.tsx b/src/components/Insights/common/InsightCard/InsightHeader/index.tsx
index 39dbf884b..495556a4d 100644
--- a/src/components/Insights/common/InsightCard/InsightHeader/index.tsx
+++ b/src/components/Insights/common/InsightCard/InsightHeader/index.tsx
@@ -1,4 +1,5 @@
import { useContext } from "react";
+import { formatTimeDistance } from "../../../../../utils/formatTimeDistance";
import {
InsightTypeInfo,
getInsightTypeInfo
@@ -74,6 +75,14 @@ export const InsightHeader = (props: InsightHeaderProps) => {
)}
+ {props.lastUpdateTimer && (
+
+ Updated:
+
+ {formatTimeDistance(props.lastUpdateTimer)}
+
+
+ )}
{props.isAsync && }
diff --git a/src/components/Insights/common/InsightCard/InsightHeader/styles.ts b/src/components/Insights/common/InsightCard/InsightHeader/styles.ts
index d29632416..91e20e933 100644
--- a/src/components/Insights/common/InsightCard/InsightHeader/styles.ts
+++ b/src/components/Insights/common/InsightCard/InsightHeader/styles.ts
@@ -1,5 +1,8 @@
import styled from "styled-components";
-import { bodySemiboldTypography } from "../../../../common/App/typographies";
+import {
+ bodySemiboldTypography,
+ footnoteRegularTypography
+} from "../../../../common/App/typographies";
export const Container = styled.div`
display: flex;
@@ -41,3 +44,10 @@ export const BadgeContainer = styled.div`
gap: 8px;
height: 24px;
`;
+
+export const Description = styled.div`
+ ${footnoteRegularTypography}
+ gap: 4px;
+ display: flex;
+ color: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
diff --git a/src/components/Insights/common/InsightCard/InsightHeader/types.ts b/src/components/Insights/common/InsightCard/InsightHeader/types.ts
index 537e2d317..a0eba2b0e 100644
--- a/src/components/Insights/common/InsightCard/InsightHeader/types.ts
+++ b/src/components/Insights/common/InsightCard/InsightHeader/types.ts
@@ -11,4 +11,5 @@ export interface InsightHeaderProps {
spanInfo?: SpanInfo | null;
onSpanLinkClick: (spanCodeObjectId: string) => void;
status?: InsightStatus;
+ lastUpdateTimer?: string | null;
}
diff --git a/src/components/Insights/common/InsightCard/RecalculateBar/RecalculateBar.stories.tsx b/src/components/Insights/common/InsightCard/RecalculateBar/RecalculateBar.stories.tsx
new file mode 100644
index 000000000..d05b62ae7
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/RecalculateBar/RecalculateBar.stories.tsx
@@ -0,0 +1,20 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { RecalculateBar } from ".";
+
+// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction
+const meta: Meta = {
+ title: "Insights/common/InsightCard/RecalculateBar",
+ component: RecalculateBar,
+ 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: {}
+};
diff --git a/src/components/Insights/common/InsightCard/RecalculateBar/index.tsx b/src/components/Insights/common/InsightCard/RecalculateBar/index.tsx
new file mode 100644
index 000000000..e8142eefb
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/RecalculateBar/index.tsx
@@ -0,0 +1,14 @@
+import { RecalculateStartedIcon } from "../../../../common/icons/16px/RecalculateStartedIcon";
+import * as s from "./styles";
+
+export const RecalculateBar = () => {
+ return (
+
+
+
+
+ Rechecking insight
+ Check here for updates
+
+ );
+};
diff --git a/src/components/Insights/common/InsightCard/RecalculateBar/styles.ts b/src/components/Insights/common/InsightCard/RecalculateBar/styles.ts
new file mode 100644
index 000000000..f93a177f4
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/RecalculateBar/styles.ts
@@ -0,0 +1,37 @@
+import styled from "styled-components";
+import {
+ caption1RegularTypography,
+ footnoteBoldTypography
+} from "../../../../common/App/typographies";
+
+export const Container = styled.div`
+ ${footnoteBoldTypography}
+
+ border-radius: 4px;
+ border: 1px solid ${({ theme }) => theme.colors.v3.status.low};
+ background: ${({ theme }) => theme.colors.v3.status.backgroundLow};
+ display: flex;
+ align-items: center;
+ padding: 6px 8px;
+ gap: 4px;
+`;
+
+export const IconContainer = styled.div`
+ color: ${({ theme }) => theme.colors.v3.status.low};
+ display: flex;
+`;
+
+export const Title = styled.span`
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+`;
+
+export const TicketStatus = styled.div`
+ color: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
+
+export const Info = styled.span`
+ ${caption1RegularTypography}
+`;
diff --git a/src/components/Insights/common/InsightCard/index.tsx b/src/components/Insights/common/InsightCard/index.tsx
index b3aef582d..118e22a3b 100644
--- a/src/components/Insights/common/InsightCard/index.tsx
+++ b/src/components/Insights/common/InsightCard/index.tsx
@@ -1,6 +1,5 @@
-import React, { useEffect, useState } from "react";
+import React, { useContext, useEffect, useState } from "react";
import { isString } from "../../../../typeGuards/isString";
-import { formatTimeDistance } from "../../../../utils/formatTimeDistance";
import { TraceIcon } from "../../../common/icons/12px/TraceIcon";
import { HistogramIcon } from "../../../common/icons/16px/HistogramIcon";
import { LiveIcon } from "../../../common/icons/16px/LiveIcon";
@@ -10,18 +9,22 @@ import { CrossIcon } from "../../../common/icons/CrossIcon";
import { Button } from "../../../common/v3/Button";
import { BaseButtonProps } from "../../../common/v3/Button/types";
import { JiraButton } from "../../../common/v3/JiraButton";
-import { Link } from "../../../common/v3/Link";
import { Tooltip } from "../../../common/v3/Tooltip";
import { isEndpointInsight, isSpanInsight } from "../../typeGuards";
import { InsightHeader } from "./InsightHeader";
import * as s from "./styles";
import { InsightCardProps } from "./types";
+import { getFeatureFlagValue } from "../../../../featureFlags";
import { usePrevious } from "../../../../hooks/usePrevious";
+import { FeatureFlag } from "../../../../types";
import { sendTrackingEvent } from "../../../../utils/sendTrackingEvent";
import { Spinner } from "../../../Navigation/CodeButtonMenu/Spinner";
+import { ConfigContext } from "../../../common/App/ConfigContext";
import { trackingEvents } from "../../tracking";
+import { InsightStatus } from "../../types";
import { ProductionAffectionBar } from "./ProductionAffectionBar";
+import { RecalculateBar } from "./RecalculateBar";
import { useDismissalHandler } from "./useDismissalHandler";
const IS_NEW_TIME_LIMIT = 1000 * 60 * 10; // in milliseconds
@@ -33,8 +36,14 @@ export const InsightCard = (props: InsightCardProps) => {
useState(false);
const { isLoading, dismiss, show } = useDismissalHandler(props.insight.id);
const previousLoading = usePrevious(isLoading);
+ const config = useContext(ConfigContext);
+ const [insightStatus, setInsightStatus] = useState(props.insight.status);
const isCritical = props.insight.criticality > HIGH_CRITICALITY_THRESHOLD;
+ // TODO: remove and refresh the insight data
+ useEffect(() => {
+ setInsightStatus(props.insight.status);
+ }, [props.insight.status]);
useEffect(() => {
if (previousLoading && !isLoading) {
@@ -42,15 +51,13 @@ export const InsightCard = (props: InsightCardProps) => {
}
}, [isLoading, previousLoading, props.onRefresh]);
- const handleRefreshLinkClick = () => {
- props.onRefresh(props.insight.type);
- };
-
const handleRecheckButtonClick = () => {
props.insight.prefixedCodeObjectId &&
props.onRecalculate &&
props.onRecalculate(props.insight.id);
setIsRecalculatingStarted(true);
+ // TODO: handle Recheck response and refresh the insight data
+ setInsightStatus(InsightStatus.InEvaluation);
};
const handleHistogramButtonClick = () => {
@@ -65,56 +72,38 @@ export const InsightCard = (props: InsightCardProps) => {
);
};
- const handleSpanLinkClick = () => {
+ const getRecalculateVisibilityParams = () => {
+ const areStartTimesEqual =
+ props.insight.customStartTime &&
+ props.insight.actualStartTime &&
+ new Date(props.insight.actualStartTime).valueOf() -
+ new Date(props.insight.customStartTime).valueOf() ===
+ 0;
+
if (
- (isSpanInsight(props.insight) || isEndpointInsight(props.insight)) &&
- props.insight.spanInfo
+ getFeatureFlagValue(config, FeatureFlag.IS_RECALCULATE_BUBBLE_ENABLED)
) {
- props.onGoToSpan(props.insight.spanInfo.spanCodeObjectId);
+ return {
+ showTimer: areStartTimesEqual,
+ showBanner: insightStatus === InsightStatus.InEvaluation
+ };
}
- };
- const renderRecalculationBlock = (
- actualStartTime: string,
- customStartTime: string | null,
- isRecalculatingStarted: boolean
- ) => {
- if (!props.insight.customStartTime && !isRecalculatingStarted) {
- return;
- }
+ return {
+ showTimer: areStartTimesEqual,
+ showBanner:
+ !areStartTimesEqual &&
+ props.insight.actualStartTime &&
+ (props.insight.customStartTime || isRecalculatingStarted)
+ };
+ };
+ const handleSpanLinkClick = () => {
if (
- isRecalculatingStarted ||
- (customStartTime && customStartTime > actualStartTime)
+ (isSpanInsight(props.insight) || isEndpointInsight(props.insight)) &&
+ props.insight.spanInfo
) {
- return (
-
-
- Applying the new time filter. Wait a few minutes and then refresh.
-
-
- Refresh
-
-
- );
- }
-
- const areStartTimesEqual =
- customStartTime &&
- new Date(actualStartTime).valueOf() -
- new Date(customStartTime).valueOf() ===
- 0;
-
- if (areStartTimesEqual) {
- const title = new Date(actualStartTime).toString();
- return (
-
- Data from:{" "}
-
- {formatTimeDistance(actualStartTime)}
-
-
- );
+ props.onGoToSpan(props.insight.spanInfo.spanCodeObjectId);
}
};
@@ -257,6 +246,8 @@ export const InsightCard = (props: InsightCardProps) => {
);
};
+ const { showBanner, showTimer } = getRecalculateVisibilityParams();
+
const isNew = isString(props.insight.firstDetected)
? Date.now() - new Date(props.insight.firstDetected).valueOf() <
IS_NEW_TIME_LIMIT
@@ -271,13 +262,14 @@ export const InsightCard = (props: InsightCardProps) => {
? props.insight.spanInfo
: undefined
}
- status={props.insight.status}
+ status={insightStatus}
isNew={isNew}
isAsync={props.isAsync}
insightType={props.insight.type}
importance={props.insight.importance}
criticality={props.insight.criticality}
onSpanLinkClick={handleSpanLinkClick}
+ lastUpdateTimer={showTimer ? props.insight.actualStartTime : null}
/>
}
content={
@@ -288,12 +280,7 @@ export const InsightCard = (props: InsightCardProps) => {
onCreateTicket={handleCreateTicketLinkClick}
/>
)}
- {props.insight.actualStartTime &&
- renderRecalculationBlock(
- props.insight.actualStartTime,
- props.insight.customStartTime,
- isRecalculatingStarted
- )}
+ {showBanner && }
{props.content}
}
diff --git a/src/components/common/App/ConfigContext.ts b/src/components/common/App/ConfigContext.ts
index c7ff5e312..79df96b8f 100644
--- a/src/components/common/App/ConfigContext.ts
+++ b/src/components/common/App/ConfigContext.ts
@@ -3,7 +3,7 @@ import { isEnvironment } from "../../../typeGuards/isEnvironment";
import { isString } from "../../../typeGuards/isString";
import { ConfigContextData } from "./types";
-export const ConfigContext = createContext({
+export const InitialData = {
digmaApiUrl: isString(window.digmaApiUrl) ? window.digmaApiUrl : "",
digmaApiProxyPrefix: isString(window.digmaApiProxyPrefix)
? window.digmaApiProxyPrefix
@@ -28,4 +28,6 @@ export const ConfigContext = createContext({
scope: undefined,
isMicrometerProject: window.isMicrometerProject === true,
state: undefined
-});
+};
+
+export const ConfigContext = createContext(InitialData);
diff --git a/src/components/common/App/typographies.ts b/src/components/common/App/typographies.ts
index e6f1850e8..bf71ff7d0 100644
--- a/src/components/common/App/typographies.ts
+++ b/src/components/common/App/typographies.ts
@@ -100,3 +100,9 @@ export const bodySemiboldTypography = css`
font-weight: ${typographies.body.fontWeight.medium};
line-height: ${typographies.body.lineHeight}px;
`;
+
+export const footnoteBoldTypography = css`
+ font-size: ${typographies.footNote.fontSize}px;
+ font-weight: ${typographies.footNote.fontWeight.bold};
+ line-height: ${typographies.footNote.lineHeight}px;
+`;
diff --git a/src/components/common/icons/16px/RecalculateStartedIcon.tsx b/src/components/common/icons/16px/RecalculateStartedIcon.tsx
new file mode 100644
index 000000000..f42a0fb0a
--- /dev/null
+++ b/src/components/common/icons/16px/RecalculateStartedIcon.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+import { useIconProps } from "../hooks";
+import { IconProps } from "../types";
+
+const RecalculateStartedIconComponent = (props: IconProps) => {
+ const { size, color } = useIconProps(props);
+
+ return (
+
+ );
+};
+
+export const RecalculateStartedIcon = React.memo(
+ RecalculateStartedIconComponent
+);
diff --git a/src/featureFlags.ts b/src/featureFlags.ts
index e5a9257a4..acf1eb7e4 100644
--- a/src/featureFlags.ts
+++ b/src/featureFlags.ts
@@ -10,6 +10,7 @@ export const featureFlagMinBackendVersions: Record = {
[FeatureFlag.IS_INSIGHT_TICKET_LINKAGE_ENABLED]: "v0.2.200",
[FeatureFlag.IS_ASSETS_COMPLEX_FILTER_ENABLED]: "v0.2.215",
[FeatureFlag.IS_INSIGHT_DISMISSAL_ENABLED]: "v0.2.238",
+ [FeatureFlag.IS_RECALCULATE_BUBBLE_ENABLED]: "v0.2.238",
[FeatureFlag.IS_ANALYTICS_TAB_VISIBLE]: "v0.2.244"
};
diff --git a/src/types.ts b/src/types.ts
index df28756f6..4e0f1158a 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -7,6 +7,7 @@ export enum FeatureFlag {
IS_INSIGHT_TICKET_LINKAGE_ENABLED,
IS_ASSETS_COMPLEX_FILTER_ENABLED,
IS_INSIGHT_DISMISSAL_ENABLED,
+ IS_RECALCULATE_BUBBLE_ENABLED,
IS_ANALYTICS_TAB_VISIBLE
}