diff --git a/src/components/Insights/ExcessiveAPICallsInsight/index.tsx b/src/components/Insights/ExcessiveAPICallsInsight/index.tsx
index e46a9d90e..a4addf574 100644
--- a/src/components/Insights/ExcessiveAPICallsInsight/index.tsx
+++ b/src/components/Insights/ExcessiveAPICallsInsight/index.tsx
@@ -62,21 +62,23 @@ export const ExcessiveAPICallsInsight = (
{config.isJaegerEnabled && traceId && (
-
- handleTraceButtonClick(
- {
- name: spanName,
- id: traceId
- },
- props.insight.type,
- spanCodeObjectId
- )
- }
- >
- Trace
-
+
+
+ handleTraceButtonClick(
+ {
+ name: spanName,
+ id: traceId
+ },
+ props.insight.type,
+ spanCodeObjectId
+ )
+ }
+ >
+ Trace
+
+
)}
);
diff --git a/src/components/Insights/common/InsightCard/InsightHeader/AsyncTag/styles.ts b/src/components/Insights/common/InsightCard/InsightHeader/AsyncTag/styles.ts
index c557d044a..d5c972b9d 100644
--- a/src/components/Insights/common/InsightCard/InsightHeader/AsyncTag/styles.ts
+++ b/src/components/Insights/common/InsightCard/InsightHeader/AsyncTag/styles.ts
@@ -1,8 +1,10 @@
import styled from "styled-components";
+import { footnoteRegularTypography } from "../../../../../common/App/typographies";
import { Tag as TagCommon } from "../../../../../common/v3/Tag";
export const AsyncTag = styled(TagCommon)`
+ ${footnoteRegularTypography}
+
color: ${({ theme }) => theme.colors.v3.text.primary};
background: ${({ theme }) => theme.colors.v3.surface.brandDark};
- font-size: 12px;
`;
diff --git a/src/components/Insights/common/InsightCard/PercentileViewModeToggle/PercentileViewModeToggle.stories.tsx b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/PercentileViewModeToggle.stories.tsx
new file mode 100644
index 000000000..5ffc91ca4
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/PercentileViewModeToggle.stories.tsx
@@ -0,0 +1,22 @@
+import { Meta, StoryObj } from "@storybook/react";
+import { PercentileViewModeToggle } 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/PercentileViewModeToggle",
+ component: PercentileViewModeToggle,
+ 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: {
+ viewMode: 0.5
+ }
+};
diff --git a/src/components/Insights/common/InsightCard/PercentileViewModeToggle/index.tsx b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/index.tsx
new file mode 100644
index 000000000..20a22c17b
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/index.tsx
@@ -0,0 +1,17 @@
+import { PERCENTILES } from "../../../../../constants";
+import { Toggle } from "../../../../common/v3/Toggle";
+import { PercentileViewModeToggleProps } from "./types";
+
+export const PercentileViewModeToggle = ({
+ viewMode,
+ onChange
+}: PercentileViewModeToggleProps) => (
+
+ options={PERCENTILES.map((percentile) => ({
+ value: percentile.percentile,
+ label: percentile.label
+ }))}
+ value={viewMode}
+ onValueChange={onChange}
+ />
+);
diff --git a/src/components/Insights/common/InsightCard/PercentileViewModeToggle/types.ts b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/types.ts
new file mode 100644
index 000000000..5ae5f64d3
--- /dev/null
+++ b/src/components/Insights/common/InsightCard/PercentileViewModeToggle/types.ts
@@ -0,0 +1,4 @@
+export interface PercentileViewModeToggleProps {
+ viewMode: number;
+ onChange: (viewMode: number) => void;
+}
diff --git a/src/components/Insights/common/InsightCard/index.tsx b/src/components/Insights/common/InsightCard/index.tsx
index 1a8033724..28592a6a6 100644
--- a/src/components/Insights/common/InsightCard/index.tsx
+++ b/src/components/Insights/common/InsightCard/index.tsx
@@ -122,40 +122,50 @@ export const InsightCard = (props: InsightCardProps) => {
{/* */}
{props.onOpenHistogram && (
-
+
+
+
)}
{props.onRecalculate && (
-
+
+
+
)}
{props.onJiraButtonClick && (
-
+
+
+
)}
{props.onPin && }
{props.onGoToTrace && (
-
diff --git a/src/components/Insights/common/insights/DurationInsight/ReferenceLineLabel/index.tsx b/src/components/Insights/common/insights/DurationInsight/ReferenceLineLabel/index.tsx
index 3e2176f58..614f0f274 100644
--- a/src/components/Insights/common/insights/DurationInsight/ReferenceLineLabel/index.tsx
+++ b/src/components/Insights/common/insights/DurationInsight/ReferenceLineLabel/index.tsx
@@ -1,16 +1,20 @@
import { Text } from "recharts";
import { CartesianViewBox } from "recharts/types/util/types";
+import { useTheme } from "styled-components";
import { isNumber } from "../../../../../../typeGuards/isNumber";
import { isString } from "../../../../../../typeGuards/isString";
import { DIVIDER, LABEL_HEIGHT } from "../constants";
import { ReferenceLineLabelProps } from "./types";
+const LABEL_GAP = 4; // in pixels
+
const isTextAnchor = (
value?: string
): value is "start" | "end" | "middle" | "inherit" | undefined =>
["start", "middle", "end", "inherit", undefined].includes(value);
export const ReferenceLineLabel = (props: ReferenceLineLabelProps) => {
+ const theme = useTheme();
const labels = isString(props.value)
? props.value.split(DIVIDER)
: isNumber(props.value)
@@ -36,8 +40,15 @@ export const ReferenceLineLabel = (props: ReferenceLineLabelProps) => {
key={text}
x={x}
textAnchor={textAnchor || "middle"}
- y={y + i * LABEL_HEIGHT}
- dy={labels.length > 1 ? -LABEL_HEIGHT : undefined}
+ y={y + i * LABEL_HEIGHT - LABEL_GAP}
+ dy={
+ labels.length > 1
+ ? -((labels.length - 1) * LABEL_HEIGHT)
+ : undefined
+ }
+ fill={theme.colors.v3.stroke.secondary}
+ fontSize={theme.typographies.captionOne.fontSize}
+ lineHeight={LABEL_HEIGHT}
>
{text}
diff --git a/src/components/Insights/common/insights/DurationInsight/XAxisTick/index.tsx b/src/components/Insights/common/insights/DurationInsight/XAxisTick/index.tsx
index 24166d69d..cb65a5a89 100644
--- a/src/components/Insights/common/insights/DurationInsight/XAxisTick/index.tsx
+++ b/src/components/Insights/common/insights/DurationInsight/XAxisTick/index.tsx
@@ -1,21 +1,10 @@
import { Text } from "recharts";
-import { DefaultTheme, useTheme } from "styled-components";
+import { useTheme } from "styled-components";
import { DIVIDER, LABEL_HEIGHT } from "../constants";
import { XAxisTickProps } from "./types";
-const getTickLabelColor = (theme: DefaultTheme) => {
- switch (theme.mode) {
- case "light":
- return "#4d668a";
- case "dark":
- case "dark-jetbrains":
- return "#dfe1e5";
- }
-};
-
export const XAxisTick = (props: XAxisTickProps) => {
const theme = useTheme();
- const tickLabelColor = getTickLabelColor(theme);
const tick = props.ticks[props.payload.value];
const textAnchor = tick.textAnchor || props.textAnchor;
@@ -29,7 +18,8 @@ export const XAxisTick = (props: XAxisTickProps) => {
{
- // const config = useContext(ConfigContext);
const theme = useTheme();
- const tickColor = theme.colors.v3.stroke.secondary;
const { observe, width } = useDimensions();
const sortedPercentiles = [...props.insight.percentiles].sort(
@@ -390,7 +388,7 @@ export const DurationInsight = (props: DurationInsightProps) => {
/>
}
interval={0}
ticks={Object.keys(ticks).map((x) => Number(x))}
@@ -403,12 +401,10 @@ export const DurationInsight = (props: DurationInsightProps) => {
= {
+ title: "Insights/common/insights/RequestBreakdownInsight",
+ component: RequestBreakdownInsight,
+ 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;
+
+const data: EndpointBreakdownInsight = {
+ sourceSpanCodeObjectInsight: "sourceSpanCodeObjectInsightId",
+ id: "60b55792-8262-4c5d-9628-7cce7979dd6d",
+ firstDetected: "2023-12-05T17:25:47.010Z",
+ lastDetected: "2024-01-05T13:14:47.010Z",
+ criticality: 0,
+ firstCommitId: "b3f7b3f",
+ lastCommitId: "a1b2c3d",
+ deactivatedCommitId: null,
+ reopenCount: 0,
+ ticketLink: null,
+ impact: 0,
+ name: "Request Breakdown",
+ type: InsightType.EndpointBreakdown,
+ category: InsightCategory.Usage,
+ specifity: 4,
+ importance: 6,
+ isRecalculateEnabled: true,
+ hasAsyncSpans: false,
+ components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.396539483729232,
+ duration: null
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.396539483729232,
+ duration: null
+ },
+ {
+ type: ComponentType.HttpClients,
+ fraction: 0.103460516270768,
+ duration: null
+ },
+ {
+ type: ComponentType.DbQueries,
+ fraction: 0.103460516270768,
+ duration: null
+ }
+ ],
+ p50Components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.396539483729232,
+ duration: null
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.396539483729232,
+ duration: null
+ },
+ {
+ type: ComponentType.HttpClients,
+ fraction: 0.103460516270768,
+ duration: null
+ },
+ {
+ type: ComponentType.DbQueries,
+ fraction: 0.103460516270768,
+ duration: null
+ }
+ ],
+ p95Components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.103460516270768,
+ duration: null
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.103460516270768,
+ duration: null
+ },
+ {
+ type: ComponentType.HttpClients,
+ fraction: 0.396539483729232,
+ duration: null
+ },
+ {
+ type: ComponentType.DbQueries,
+ fraction: 0.396539483729232,
+ duration: null
+ }
+ ],
+ scope: InsightScope.EntrySpan,
+ endpointSpan: "HTTP GET /owners/new",
+ spanCodeObjectId: "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/new",
+ route: "epHTTP:HTTP GET /owners/new",
+ serviceName: "PetClinic",
+ spanInfo: {
+ name: "HTTP GET /owners/new",
+ displayName: "HTTP GET /owners/new",
+ instrumentationLibrary: "io.opentelemetry.tomcat-10.0",
+ spanCodeObjectId:
+ "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /owners/new",
+ methodCodeObjectId:
+ "method:org.springframework.samples.petclinic.owner.OwnerController$_$initCreationForm",
+ kind: "Server",
+ codeObjectId:
+ "org.springframework.samples.petclinic.owner.OwnerController$_$initCreationForm"
+ },
+ shortDisplayInfo: {
+ title: "Request Breakdown",
+ targetDisplayName: "",
+ subtitle: "",
+ description: ""
+ },
+ codeObjectId:
+ "org.springframework.samples.petclinic.owner.OwnerController$_$initCreationForm",
+ decorators: null,
+ environment: "SAMPLE_ENV",
+ severity: 0,
+ prefixedCodeObjectId:
+ "method:org.springframework.samples.petclinic.owner.OwnerController$_$initCreationForm",
+ customStartTime: null,
+ actualStartTime: "2023-06-30T00:00:00.000Z"
+};
+
+export const Default: Story = {
+ args: {
+ insight: data
+ }
+};
+
+export const Async: Story = {
+ args: {
+ insight: {
+ ...data,
+ components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.996539483729232,
+ duration: {
+ value: 2.06,
+ unit: "ms",
+ raw: 2063332.9999999993
+ }
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.0034605162707679665,
+ duration: {
+ value: 1.03,
+ unit: "ms",
+ raw: 1031666.4999999995
+ }
+ }
+ ],
+ p50Components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.996539483729232,
+ duration: {
+ value: 2.06,
+ unit: "ms",
+ raw: 2063332.9999999993
+ }
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.0034605162707679665,
+ duration: {
+ value: 1.03,
+ unit: "ms",
+ raw: 1031666.4999999995
+ }
+ }
+ ],
+ p95Components: [
+ {
+ type: ComponentType.Internal,
+ fraction: 0.996539483729232,
+ duration: {
+ value: 1.06,
+ unit: "ms",
+ raw: 1063332.9999999993
+ }
+ },
+ {
+ type: ComponentType.Rendering,
+ fraction: 0.0034605162707679665,
+ duration: {
+ value: 2.03,
+ unit: "ms",
+ raw: 2031666.4999999995
+ }
+ }
+ ],
+ hasAsyncSpans: true
+ }
+ }
+};
diff --git a/src/components/Insights/common/insights/RequestBreakdownInsight/index.tsx b/src/components/Insights/common/insights/RequestBreakdownInsight/index.tsx
new file mode 100644
index 000000000..0ecb0922c
--- /dev/null
+++ b/src/components/Insights/common/insights/RequestBreakdownInsight/index.tsx
@@ -0,0 +1,240 @@
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ useReactTable
+} from "@tanstack/react-table";
+import { useMemo, useState } from "react";
+import { Cell, Pie, PieChart } from "recharts";
+import { useTheme } from "styled-components";
+import { DefaultTheme } from "styled-components/dist/types";
+import { getDurationString } from "../../../../../utils/getDurationString";
+import { roundTo } from "../../../../../utils/roundTo";
+import { Tag } from "../../../../common/v3/Tag";
+import { Tooltip } from "../../../../common/v3/Tooltip";
+import {
+ Component,
+ ComponentType,
+ EndpointBreakdownInsight
+} from "../../../types";
+import { InsightCard } from "../../InsightCard";
+import { PercentileViewModeToggle } from "../../InsightCard/PercentileViewModeToggle";
+import * as s from "./styles";
+import { RequestBreakdownInsightProps } from "./types";
+
+const PIE_CHART_RADIUS = 50;
+const PIE_CHART_ARC_WIDTH = 4;
+
+const getComponentTypeColors = (theme: DefaultTheme) => ({
+ [ComponentType.Internal]: {
+ fill: theme.colors.v3.pieChart.brightPurpleFill,
+ stroke: theme.colors.v3.pieChart.brightPurpleStroke
+ },
+ [ComponentType.DbQueries]: {
+ fill: theme.colors.v3.pieChart.pinkFill,
+ stroke: theme.colors.v3.pieChart.pinkStroke
+ },
+ [ComponentType.HttpClients]: {
+ fill: theme.colors.v3.pieChart.brightOrangeFill,
+ stroke: theme.colors.v3.pieChart.brightOrangeStroke
+ },
+ [ComponentType.Rendering]: {
+ fill: theme.colors.v3.pieChart.azureFill,
+ stroke: theme.colors.v3.pieChart.azureStroke
+ }
+});
+
+const DEFAULT_PERCENTILE = 0.5;
+
+const getComponents = (
+ insight: EndpointBreakdownInsight,
+ percentile: number
+): Component[] | undefined => {
+ switch (percentile) {
+ case 0.5:
+ return insight.p50Components || undefined;
+ case 0.95:
+ return insight.p95Components || undefined;
+ default:
+ return undefined;
+ }
+};
+
+const sortByType = (a: Component, b: Component) =>
+ String(a.type).localeCompare(String(b.type));
+
+const columnHelper = createColumnHelper();
+
+export const RequestBreakdownInsight = (
+ props: RequestBreakdownInsightProps
+) => {
+ const theme = useTheme();
+ const componentTypeColors = getComponentTypeColors(theme);
+
+ const [percentileViewMode, setPercentileViewMode] =
+ useState(DEFAULT_PERCENTILE);
+
+ const data = useMemo(() => {
+ const components =
+ getComponents(props.insight, percentileViewMode) ||
+ props.insight.components;
+
+ const sortedComponents = props.insight.hasAsyncSpans
+ ? [...components].sort((a, b) =>
+ a.duration && b.duration
+ ? a.duration.raw - b.duration.raw
+ : sortByType(a, b)
+ )
+ : [...components].sort(
+ (a, b) => b.fraction - a.fraction || sortByType(a, b)
+ );
+
+ return sortedComponents;
+ }, [props.insight, percentileViewMode]);
+
+ const handlePercentileViewModeChange = (value: number) => {
+ setPercentileViewMode(value);
+ };
+
+ const PIE_CHART_SIZE = PIE_CHART_RADIUS * 2 + PIE_CHART_ARC_WIDTH;
+ const STROKE_WIDTH = PIE_CHART_ARC_WIDTH / 2;
+
+ const renderPieChart = () => (
+
+
+
+
+ {data.map((entry) => (
+ |
+ ))}
+
+
+
+
+ {data.map((x) => (
+
+
+ {x.type}
+
+ {roundTo(x.fraction * 100, 2)}%
+
+
+ ))}
+
+
+ );
+
+ const maxDuration = data.reduce(
+ (acc, cur) =>
+ cur.duration && cur.duration.raw > acc ? cur.duration.raw : acc,
+ 0
+ );
+
+ const columns = [
+ columnHelper.accessor("type", {
+ header: "Category",
+ cell: (info) => {
+ const category = info.getValue();
+ return {category};
+ }
+ }),
+ columnHelper.accessor("duration", {
+ id: "duration",
+ header: "Request Time",
+ cell: (info) => {
+ const duration = info.getValue();
+ const value = duration && maxDuration ? duration.raw / maxDuration : 0;
+ const durationString = duration ? getDurationString(duration) : "";
+
+ return (
+
+
+
+
+ {duration && (
+
+
+
+ )}
+
+ );
+ }
+ })
+ ];
+
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel()
+ });
+
+ const renderTable = () => (
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))}
+
+
+ );
+
+ return (
+
+ {props.insight.p50Components && props.insight.p95Components && (
+
+ )}
+ {props.insight.hasAsyncSpans ? renderTable() : renderPieChart()}
+
+ }
+ onRecalculate={props.onRecalculate}
+ onRefresh={props.onRefresh}
+ isAsync={props.insight.hasAsyncSpans}
+ />
+ );
+};
diff --git a/src/components/Insights/common/insights/RequestBreakdownInsight/styles.ts b/src/components/Insights/common/insights/RequestBreakdownInsight/styles.ts
new file mode 100644
index 000000000..01befc608
--- /dev/null
+++ b/src/components/Insights/common/insights/RequestBreakdownInsight/styles.ts
@@ -0,0 +1,134 @@
+import styled from "styled-components";
+import { caption1RegularTypography } from "../../../../common/App/typographies";
+import {
+ FractionProgressBarValueProps,
+ LegendItemDataColorBadgeProps
+} from "./types";
+
+const PROGRESS_BAR_HEIGHT = 6; // in pixels
+const PROGRESS_BAR_VALUE_HEIGHT = 2; // in pixels
+
+export const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+`;
+
+export const ContentContainer = styled.div`
+ display: flex;
+ gap: 20px;
+ align-items: center;
+ padding: 4px;
+`;
+
+export const PieChartContainer = styled.div`
+ padding: 3px;
+ border-radius: 50%;
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.tertiary};
+ background: ${({ theme }) => theme.colors.v3.surface.secondary};
+`;
+
+export const Legend = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
+ flex-grow: 1;
+`;
+
+export const LegendItem = styled.div`
+ ${caption1RegularTypography}
+
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`;
+
+export const LegendItemDataColorBadge = styled.div`
+ height: 10px;
+ width: 10px;
+ border-radius: 1px;
+ background: ${({ $colors }) => $colors.background};
+ border: 2px solid ${({ $colors }) => $colors.border};
+ box-sizing: border-box;
+`;
+
+export const LegendItemDataLabel = styled.span`
+ color: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
+
+export const LegendItemDataValue = styled.span`
+ margin-left: auto;
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+`;
+
+export const Table = styled.table`
+ width: 100%;
+ border-spacing: 0 8px;
+`;
+
+export const TableHead = styled.thead`
+ color: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
+
+export const TableHeaderCell = styled.th`
+ ${caption1RegularTypography}
+
+ &:first-child {
+ text-align: left;
+ padding-left: 8px;
+ }
+
+ &:last-child {
+ text-align: right;
+ padding-right: 8px;
+ }
+`;
+
+export const TableBodyRow = styled.tr`
+ background: ${({ theme }) => theme.colors.v3.surface.sidePanelHeader};
+`;
+
+export const TableBodyCell = styled.td`
+ &:first-child {
+ border-radius: 4px 0 0 4px;
+ padding: 6px 4px 6px 8px;
+ }
+
+ &:last-child {
+ border-radius: 0 4px 4px 0;
+ width: 100%;
+ padding: 6px 4px;
+ }
+`;
+
+export const CategoryName = styled.span`
+ ${caption1RegularTypography}
+ color: ${({ theme }) => theme.colors.v3.text.primary};
+ display: flex;
+ white-space: nowrap;
+`;
+
+export const RequestTimeContainer = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`;
+
+export const FractionProgressBar = styled.div`
+ height: ${PROGRESS_BAR_HEIGHT}px;
+ border-radius: ${PROGRESS_BAR_HEIGHT / 2}px;
+ flex-grow: 1;
+ position: relative;
+ background: ${({ theme }) => theme.colors.v3.surface.highlight};
+ padding: 2px;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+`;
+
+export const FractionProgressBarValue = styled.div`
+ width: ${({ $value }) => Math.floor($value * 100)}%;
+ height: ${PROGRESS_BAR_VALUE_HEIGHT}px;
+ border-radius: ${PROGRESS_BAR_VALUE_HEIGHT / 2}px;
+ background: ${({ theme }) => theme.colors.v3.text.secondary};
+`;
diff --git a/src/components/Insights/common/insights/RequestBreakdownInsight/types.ts b/src/components/Insights/common/insights/RequestBreakdownInsight/types.ts
new file mode 100644
index 000000000..4f21d83b5
--- /dev/null
+++ b/src/components/Insights/common/insights/RequestBreakdownInsight/types.ts
@@ -0,0 +1,16 @@
+import { EndpointBreakdownInsight, InsightProps } from "../../../types";
+
+export interface RequestBreakdownInsightProps extends InsightProps {
+ insight: EndpointBreakdownInsight;
+}
+
+export interface LegendItemDataColorBadgeProps {
+ $colors: {
+ background: string;
+ border: string;
+ };
+}
+
+export interface FractionProgressBarValueProps {
+ $value: number;
+}
diff --git a/src/components/common/App/themes/darkTheme.ts b/src/components/common/App/themes/darkTheme.ts
index 4c2c9b1f4..3ed52c6ee 100644
--- a/src/components/common/App/themes/darkTheme.ts
+++ b/src/components/common/App/themes/darkTheme.ts
@@ -233,7 +233,9 @@ export const darkTheme: ThemeColors = {
secondary: v3colors.gray[1500],
brandPrimary: v3colors.primary[500],
brandSecondary: v3colors.primary[300],
+ brandTertiary: v3colors.primary[400],
brandDark: v3colors.primary[900],
+ brandDarkest: v3colors.primary[1000],
sidePanelHeader: v3colors.gray[1200]
},
text: {
diff --git a/src/components/common/App/themes/lightTheme.ts b/src/components/common/App/themes/lightTheme.ts
index 19347bd47..ba3b4ccfc 100644
--- a/src/components/common/App/themes/lightTheme.ts
+++ b/src/components/common/App/themes/lightTheme.ts
@@ -231,7 +231,9 @@ export const lightTheme: ThemeColors = {
secondary: v3colors.gray[0],
brandPrimary: v3colors.primary[500],
brandSecondary: v3colors.primary[300],
+ brandTertiary: v3colors.primary[400],
brandDark: v3colors.primary[150],
+ brandDarkest: v3colors.primary[100],
sidePanelHeader: v3colors.gray[300]
},
text: {
diff --git a/src/components/common/v3/Button/styles.ts b/src/components/common/v3/Button/styles.ts
index 99384e709..fb1c32a21 100644
--- a/src/components/common/v3/Button/styles.ts
+++ b/src/components/common/v3/Button/styles.ts
@@ -1,5 +1,4 @@
import styled, { css } from "styled-components";
-import { v3colors } from "../../App/v3colors";
import { ButtonElementProps } from "./types";
export const Button = styled.button`
@@ -22,7 +21,7 @@ export const Button = styled.button`
return theme.colors.v3.surface.primary;
case "primary":
default:
- return v3colors.primary[400];
+ return theme.colors.v3.surface.brandTertiary;
}
}};
@@ -96,7 +95,7 @@ export const Button = styled.button`
return theme.colors.v3.surface.brandDark;
case "primary":
default:
- return v3colors.primary[300];
+ return theme.colors.v3.surface.secondary;
}
}};
border: 1px solid
@@ -108,7 +107,7 @@ export const Button = styled.button`
return theme.colors.v3.stroke.primary;
case "primary":
default:
- return v3colors.primary[300];
+ return theme.colors.v3.surface.secondary;
}
}};
@@ -139,7 +138,7 @@ export const Button = styled.button`
return theme.colors.v3.surface.primary;
case "primary":
default:
- return v3colors.primary[500];
+ return theme.colors.v3.surface.primary;
}
}};
border: 1px solid
@@ -151,7 +150,7 @@ export const Button = styled.button`
return theme.colors.v3.stroke.primary;
case "primary":
default:
- return v3colors.primary[300];
+ return theme.colors.v3.surface.secondary;
}
}};
diff --git a/src/components/common/v3/Toggle/index.tsx b/src/components/common/v3/Toggle/index.tsx
new file mode 100644
index 000000000..fa321afbe
--- /dev/null
+++ b/src/components/common/v3/Toggle/index.tsx
@@ -0,0 +1,22 @@
+import * as s from "./styles";
+import { ToggleProps, ToggleValue } from "./types";
+
+export const Toggle = (props: ToggleProps) => {
+ const handleOptionButtonClick = (value: T) => {
+ props.onValueChange(value);
+ };
+
+ return (
+
+ {props.options.map((option) => (
+ handleOptionButtonClick(option.value)}
+ >
+ {option.label}
+
+ ))}
+
+ );
+};
diff --git a/src/components/common/v3/Toggle/styles.ts b/src/components/common/v3/Toggle/styles.ts
new file mode 100644
index 000000000..ddd4ccb5e
--- /dev/null
+++ b/src/components/common/v3/Toggle/styles.ts
@@ -0,0 +1,29 @@
+import styled from "styled-components";
+import { footnoteRegularTypography } from "../../App/typographies";
+import { OptionButtonProps } from "./types";
+
+export const Container = styled.div`
+ display: flex;
+ border-radius: 4px;
+ padding: 3px;
+ gap: 4px;
+ width: fit-content;
+ border: 1px solid ${({ theme }) => theme.colors.v3.stroke.tertiary};
+ background: ${({ theme }) => theme.colors.v3.surface.secondary};
+`;
+
+export const OptionButton = styled.button`
+ ${footnoteRegularTypography}
+
+ font-family: inherit;
+ border: none;
+ outline: none;
+ border-radius: 4px;
+ padding: 2px 4px;
+ cursor: pointer;
+ user-select: none;
+ color: ${({ theme, $selected }) =>
+ $selected ? theme.colors.v3.text.white : theme.colors.v3.text.primary};
+ background: ${({ theme, $selected }) =>
+ $selected ? theme.colors.v3.surface.brandTertiary : "transparent"};
+`;
diff --git a/src/components/common/v3/Toggle/types.ts b/src/components/common/v3/Toggle/types.ts
new file mode 100644
index 000000000..bd726acb1
--- /dev/null
+++ b/src/components/common/v3/Toggle/types.ts
@@ -0,0 +1,16 @@
+export type ToggleValue = string | number;
+
+export interface ToggleOption {
+ value: T;
+ label: string;
+}
+
+export interface ToggleProps {
+ options: ToggleOption[];
+ onValueChange: (value: T) => void;
+ value: T;
+}
+
+export interface OptionButtonProps {
+ $selected: boolean;
+}
diff --git a/src/styled.d.ts b/src/styled.d.ts
index dbca0ba82..786de5a05 100644
--- a/src/styled.d.ts
+++ b/src/styled.d.ts
@@ -70,7 +70,9 @@ export interface ThemeColors {
secondary: string;
brandPrimary: string;
brandSecondary: string;
+ brandTertiary: string;
brandDark: string;
+ brandDarkest: string;
sidePanelHeader: string;
};
text: {
@@ -91,7 +93,6 @@ export interface ThemeColors {
brandSecondary: string;
};
icon: {
- white: string;
primary: string;
secondary: string;
tertiary: string;
@@ -99,6 +100,7 @@ export interface ThemeColors {
brandPrimary: string;
brandSecondary: string;
brandTertiary: string;
+ white: string;
};
status: {
backgroundHigh: string;
@@ -110,11 +112,6 @@ export interface ThemeColors {
backgroundSuccess: string;
success: string;
};
- barChart: {
- pink: string;
- purple: string;
- blue: string;
- };
pieChart: {
pinkFill: string;
pinkStroke: string;
@@ -125,6 +122,11 @@ export interface ThemeColors {
brightOrangeFill: string;
brightOrangeStroke: string;
};
+ barChart: {
+ pink: string;
+ purple: string;
+ blue: string;
+ };
};
}