diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/EnvironmentChip.stories.tsx b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/EnvironmentChip.stories.tsx
index ff268d8c7..d907a0cdf 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/EnvironmentChip.stories.tsx
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/EnvironmentChip.stories.tsx
@@ -37,3 +37,39 @@ export const Active: Story = {
isActive: true
}
};
+
+export const WithHighCriticalIssues: Story = {
+ args: {
+ environment: mockedEnvironment,
+ isActive: false,
+ issueCounts: {
+ highCriticality: 1,
+ mediumCriticality: 2,
+ lowCriticality: 3
+ }
+ }
+};
+
+export const WithMediumCriticalIssues: Story = {
+ args: {
+ environment: mockedEnvironment,
+ isActive: false,
+ issueCounts: {
+ highCriticality: 0,
+ mediumCriticality: 2,
+ lowCriticality: 3
+ }
+ }
+};
+
+export const WithLowCriticalIssues: Story = {
+ args: {
+ environment: mockedEnvironment,
+ isActive: false,
+ issueCounts: {
+ highCriticality: 0,
+ mediumCriticality: 0,
+ lowCriticality: 3
+ }
+ }
+};
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/index.tsx b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/index.tsx
index 2c799fc5d..1f4fb4da1 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/index.tsx
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/index.tsx
@@ -1,10 +1,12 @@
import { EnvironmentIcon } from "../../../../common/EnvironmentIcon";
import { Tooltip } from "../../../../common/v3/Tooltip";
+import { getMostCriticalIssueCount } from "../getMostCriticalIssueCount";
import * as s from "./styles";
import { EnvironmentChipProps } from "./types";
export const EnvironmentChip = ({
environment,
+ issueCounts,
onClick,
isActive
}: EnvironmentChipProps) => {
@@ -15,14 +17,18 @@ export const EnvironmentChip = ({
};
const environmentName = environment.name;
+ const count = getMostCriticalIssueCount(issueCounts);
return (
-
+
{environmentName}
+ {count && (
+ {count.count}
+ )}
);
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/styles.ts b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/styles.ts
index 6dccb6ea0..2eb8df3dc 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/styles.ts
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/styles.ts
@@ -1,13 +1,15 @@
import styled, { css } from "styled-components";
+import { footnoteRegularTypography } from "../../../../common/App/typographies";
import { Chip } from "../../../../common/Chip";
import { activeStyles } from "../../../../common/Chip/styles";
-import { StyledChipProps } from "./types";
+import { CounterProps, ISSUE_CRITICALITY, StyledChipProps } from "./types";
+
export const StyledChip = styled(Chip)`
gap: 4px;
- ${({ isActive }) => (isActive ? activeStyles : "")}
- ${({ isActive }) =>
- isActive
+ ${({ $isActive }) => ($isActive ? activeStyles : "")}
+ ${({ $isActive }) =>
+ $isActive
? css`
cursor: initial;
`
@@ -25,3 +27,33 @@ export const Name = styled.span`
white-space: nowrap;
text-overflow: ellipsis;
`;
+
+export const Counter = styled.div`
+ ${footnoteRegularTypography}
+
+ height: 16px;
+ display: flex;
+ justify-content: center;
+ padding: 0 4px;
+ color: ${({ theme }) => theme.colors.v3.text.white};
+ border-radius: 2px;
+ margin-left: auto;
+
+ ${({ $criticality }) => {
+ switch ($criticality) {
+ case ISSUE_CRITICALITY.HIGH:
+ return css`
+ background: ${({ theme }) => theme.colors.v3.status.high};
+ `;
+ case ISSUE_CRITICALITY.MEDIUM:
+ return css`
+ background: ${({ theme }) => theme.colors.v3.status.medium};
+ `;
+ case ISSUE_CRITICALITY.LOW:
+ default:
+ return css`
+ background: ${({ theme }) => theme.colors.v3.status.low};
+ `;
+ }
+ }}
+`;
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/types.ts b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/types.ts
index f97888d8a..9b84265e8 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/types.ts
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/EnvironmentChip/types.ts
@@ -1,11 +1,25 @@
-import { Environment } from "../../../../common/App/types";
+import {
+ Environment,
+ EnvironmentIssueCounts
+} from "../../../../common/App/types";
+
+export enum ISSUE_CRITICALITY {
+ LOW = 1,
+ MEDIUM = 2,
+ HIGH = 3
+}
export interface EnvironmentChipProps {
environment: Environment;
onClick: (environment: string) => void;
isActive: boolean;
+ issueCounts?: EnvironmentIssueCounts;
}
export interface StyledChipProps {
- isActive: boolean;
+ $isActive: boolean;
+}
+
+export interface CounterProps {
+ $criticality: ISSUE_CRITICALITY;
}
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/getMostCriticalIssueCount.ts b/src/components/Insights/InsightsCatalog/EnvironmentSelector/getMostCriticalIssueCount.ts
new file mode 100644
index 000000000..0f7d6fe02
--- /dev/null
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/getMostCriticalIssueCount.ts
@@ -0,0 +1,35 @@
+import { EnvironmentIssueCounts } from "../../../common/App/types";
+import { ISSUE_CRITICALITY } from "./EnvironmentChip/types";
+
+export const getMostCriticalIssueCount = (
+ counts?: EnvironmentIssueCounts
+):
+ | {
+ count: number;
+ criticality: ISSUE_CRITICALITY;
+ }
+ | undefined => {
+ if (!counts) {
+ return undefined;
+ }
+
+ if (counts.highCriticality) {
+ return {
+ count: counts.highCriticality,
+ criticality: ISSUE_CRITICALITY.HIGH
+ };
+ }
+
+ if (counts.mediumCriticality) {
+ return {
+ count: counts.mediumCriticality,
+ criticality: ISSUE_CRITICALITY.MEDIUM
+ };
+ }
+
+ if (counts.lowCriticality) {
+ return { count: counts.lowCriticality, criticality: ISSUE_CRITICALITY.LOW };
+ }
+
+ return undefined;
+};
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/index.tsx b/src/components/Insights/InsightsCatalog/EnvironmentSelector/index.tsx
index 66fb0122e..6e25f73d7 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/index.tsx
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/index.tsx
@@ -9,8 +9,9 @@ import { NewButton } from "../../../common/v3/NewButton";
import { EnvironmentMenu } from "../../../Navigation/EnvironmentBar/EnvironmentMenu";
import { trackingEvents } from "../../tracking";
import { EnvironmentChip } from "./EnvironmentChip";
+import { getMostCriticalIssueCount } from "./getMostCriticalIssueCount";
import * as s from "./styles";
-import { EnvironmentSelectorProps } from "./types";
+import { EnvironmentSelectorProps, SelectorEnvironment } from "./types";
const ENVIRONMENT_CHIP_COUNT = 3;
@@ -26,6 +27,35 @@ const getSlidingWindow = (arr: T[], start: number, length: number) => {
return result;
};
+const sortEnvironmentsByCriticalIssues = (
+ a: SelectorEnvironment,
+ b: SelectorEnvironment
+) => {
+ const aCount = getMostCriticalIssueCount(a.issueCounts);
+ const bCount = getMostCriticalIssueCount(b.issueCounts);
+
+ if (aCount && !bCount) {
+ return -1;
+ }
+
+ if (!aCount && bCount) {
+ return 1;
+ }
+
+ if (aCount && bCount) {
+ if (aCount.criticality === bCount.criticality) {
+ return (
+ bCount.count - aCount.count ||
+ a.environment.name.localeCompare(b.environment.name)
+ );
+ }
+
+ return bCount.criticality - aCount.criticality;
+ }
+
+ return 0;
+};
+
export const EnvironmentSelector = ({
environments
}: EnvironmentSelectorProps) => {
@@ -33,8 +63,11 @@ export const EnvironmentSelector = ({
const environment = useGlobalStore.use.environment();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { observe, width } = useDimensions();
+ const sortedEnvironments = environments.sort(
+ sortEnvironmentsByCriticalIssues
+ );
- if (environments.length < 2) {
+ if (sortedEnvironments.length < 2) {
return null;
}
@@ -70,24 +103,24 @@ export const EnvironmentSelector = ({
setIsMenuOpen(!isMenuOpen);
};
- const environmentIndex = environments.findIndex(
- (x) => x.id === environment?.id
+ const environmentIndex = sortedEnvironments.findIndex(
+ (x) => x.environment.id === environment?.id
);
const environmentsWithChips =
- environments.length > ENVIRONMENT_CHIP_COUNT
+ sortedEnvironments.length > ENVIRONMENT_CHIP_COUNT
? getSlidingWindow(
- environments,
+ sortedEnvironments,
environmentIndex - 1,
ENVIRONMENT_CHIP_COUNT
)
- : environments;
+ : sortedEnvironments;
const renderEnvironmentMenuButton = () => (
);
@@ -96,21 +129,22 @@ export const EnvironmentSelector = ({
{environmentsWithChips.map((x) => (
))}
- {environments.length > ENVIRONMENT_CHIP_COUNT && (
+ {sortedEnvironments.length > ENVIRONMENT_CHIP_COUNT && (
<>
{/* // TODO: refactor this to use only popover */}
{isMenuOpen ? (
x.environment)}
onMenuItemClick={handleMenuItemClick}
/>
}
diff --git a/src/components/Insights/InsightsCatalog/EnvironmentSelector/types.ts b/src/components/Insights/InsightsCatalog/EnvironmentSelector/types.ts
index 51a8eb487..53a5eea5e 100644
--- a/src/components/Insights/InsightsCatalog/EnvironmentSelector/types.ts
+++ b/src/components/Insights/InsightsCatalog/EnvironmentSelector/types.ts
@@ -1,5 +1,10 @@
-import { Environment } from "../../../common/App/types";
+import { Environment, EnvironmentIssueCounts } from "../../../common/App/types";
+
+export interface SelectorEnvironment {
+ environment: Environment;
+ issueCounts?: EnvironmentIssueCounts;
+}
export interface EnvironmentSelectorProps {
- environments: Environment[];
+ environments: SelectorEnvironment[];
}
diff --git a/src/components/Insights/InsightsCatalog/index.tsx b/src/components/Insights/InsightsCatalog/index.tsx
index b379b4872..af25f3f32 100644
--- a/src/components/Insights/InsightsCatalog/index.tsx
+++ b/src/components/Insights/InsightsCatalog/index.tsx
@@ -31,6 +31,7 @@ import { Tooltip } from "../../common/v3/Tooltip";
import { IssuesFilter } from "../Issues/IssuesFilter";
import { trackingEvents } from "../tracking";
import { EnvironmentSelector } from "./EnvironmentSelector";
+import { SelectorEnvironment } from "./EnvironmentSelector/types";
import { FilterButton } from "./FilterButton";
import { FilterPanel } from "./FilterPanel";
import { InsightsPage } from "./InsightsPage";
@@ -112,6 +113,17 @@ export const InsightsCatalog = ({
"application"
);
+ const appliedFilterCount =
+ filters.length + (filteredInsightTypes.length > 0 ? 1 : 0);
+
+ const areSpanEnvironmentsEnabled = getFeatureFlagValue(
+ backendInfo,
+ FeatureFlag.ARE_SPAN_ENVIRONMENTS_ENABLED
+ );
+ const selectorEnvironments: SelectorEnvironment[] = areSpanEnvironmentsEnabled
+ ? insightStats?.spanEnvironments ?? []
+ : environments?.map((x) => ({ environment: x })) ?? [];
+
const handleRegistrationComplete = () => {
sendUserActionTrackingEvent(
mainTrackingEvents.PROMOTION_REGISTRATION_FORM_SUBMITTED
@@ -245,15 +257,12 @@ export const InsightsCatalog = ({
);
};
- const appliedFilterCount =
- filters.length + (filteredInsightTypes.length > 0 ? 1 : 0);
-
return (
<>
- {isAtSpan && environments && environments.length > 1 && (
-
+ {isAtSpan && selectorEnvironments.length > 1 && (
+
)}
{!isAtSpan && renderFilterPanel()}
diff --git a/src/components/Insights/InsightsCatalog/styles.ts b/src/components/Insights/InsightsCatalog/styles.ts
index 65e03eff3..ccdf32114 100644
--- a/src/components/Insights/InsightsCatalog/styles.ts
+++ b/src/components/Insights/InsightsCatalog/styles.ts
@@ -113,4 +113,5 @@ export const ToolbarButtonsContainer = styled.div`
display: flex;
gap: 4px;
align-items: center;
+ margin-left: auto;
`;
diff --git a/src/components/Insights/insightTickets/common/useLoading.ts b/src/components/Insights/insightTickets/common/useLoading.ts
index b77d0e505..8c71e4070 100644
--- a/src/components/Insights/insightTickets/common/useLoading.ts
+++ b/src/components/Insights/insightTickets/common/useLoading.ts
@@ -11,9 +11,12 @@ export const useLoading = (
return;
}
- const timerId = setTimeout(() => setIsLoading(false), cancelTimeoutMs);
+ const timerId = window.setTimeout(
+ () => setIsLoading(false),
+ cancelTimeoutMs
+ );
return () => {
- clearTimeout(timerId);
+ window.clearTimeout(timerId);
};
}, []);
diff --git a/src/components/Insights/useInsightsData.ts b/src/components/Insights/useInsightsData.ts
index 11004db56..ad61817be 100644
--- a/src/components/Insights/useInsightsData.ts
+++ b/src/components/Insights/useInsightsData.ts
@@ -204,9 +204,8 @@ export const useInsightsData = ({
}, [getDataListParams, getStatsParams, setIsLoading, isAppReadyToGetData]);
useEffect(() => {
- const timerId = refreshTimerId.current;
return () => {
- window.clearTimeout(timerId);
+ window.clearTimeout(refreshTimerId.current);
};
}, []);
diff --git a/src/components/common/App/types.ts b/src/components/common/App/types.ts
index 167b1043e..be476acde 100644
--- a/src/components/common/App/types.ts
+++ b/src/components/common/App/types.ts
@@ -54,6 +54,17 @@ export interface ScopeSpan {
role: "Entry" | "Internal" | "Unknown" | null;
}
+export interface EnvironmentIssueCounts {
+ highCriticality: number;
+ mediumCriticality: number;
+ lowCriticality: number;
+}
+
+export interface SpanEnvironment {
+ environment: Environment;
+ issueCounts: EnvironmentIssueCounts;
+}
+
export interface Scope {
span: ScopeSpan | null;
code: {
@@ -163,6 +174,7 @@ export interface InsightStats {
unreadInsightsCount: number;
criticalInsightsCount?: number;
allIssuesCount?: number;
+ spanEnvironments?: SpanEnvironment[];
}
export interface UserInfo {
diff --git a/src/featureFlags.ts b/src/featureFlags.ts
index 3b985866a..4d5d167de 100644
--- a/src/featureFlags.ts
+++ b/src/featureFlags.ts
@@ -10,6 +10,7 @@ export const featureFlagMinBackendVersions: Record = {
[FeatureFlag.IS_HIGHLIGHTS_SPAN_INFO_ENABLED]: "0.3.19",
[FeatureFlag.IS_DURATION_BREAKDOWN_QUANTITY_ENABLED]: "0.3.34",
[FeatureFlag.ARE_ISSUES_FILTERS_ENABLED]: "0.3.72",
+ [FeatureFlag.ARE_SPAN_ENVIRONMENTS_ENABLED]: "0.3.95",
[FeatureFlag.IS_REPORT_ENABLED]: "0.3.95"
};
diff --git a/src/hooks/useFetchData.ts b/src/hooks/useFetchData.ts
index ec3e1eefe..909aa555f 100644
--- a/src/hooks/useFetchData.ts
+++ b/src/hooks/useFetchData.ts
@@ -96,7 +96,6 @@ export const useFetchData = (
]);
useEffect(() => {
- const timerId = refreshTimerId.current;
const handleData = (data: unknown, timeStamp: number) => {
if (isInProgress) {
if (handleResponse) {
@@ -111,7 +110,7 @@ export const useFetchData = (
return () => {
dispatcher.removeActionListener(responseAction, handleData);
- window.clearTimeout(timerId);
+ window.clearTimeout(refreshTimerId.current);
};
}, [responseAction, isInProgress, handleResponse]);
diff --git a/src/types.ts b/src/types.ts
index 98fe0f2cb..596b2ab48 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -12,6 +12,7 @@ export enum FeatureFlag {
ARE_NEW_INSTRUMENTATION_ATTRIBUTES_ENABLED,
IS_DURATION_BREAKDOWN_QUANTITY_ENABLED,
ARE_ISSUES_FILTERS_ENABLED,
+ ARE_SPAN_ENVIRONMENTS_ENABLED,
IS_REPORT_ENABLED
}