From f25b95da6826ac82fd311dbfbc4f19aea63c7e56 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 11 Dec 2023 17:03:32 +0100 Subject: [PATCH 1/5] Restyle Recent Activity --- src/components/RecentActivity/Badge/index.tsx | 17 ++ src/components/RecentActivity/Badge/styles.ts | 21 ++ src/components/RecentActivity/Badge/types.ts | 13 + .../EnvironmentPanel/EnvironmentTab/index.tsx | 18 +- .../EnvironmentPanel/EnvironmentTab/styles.ts | 85 ++---- .../EnvironmentPanel/EnvironmentTab/types.ts | 19 ++ .../RecentActivity/EnvironmentPanel/index.tsx | 118 ++++---- .../RecentActivity/EnvironmentPanel/styles.ts | 62 ++-- .../RecentActivity/EnvironmentPanel/types.ts | 2 - .../RecentActivity/LiveView/index.tsx | 16 +- .../RecentActivity/LiveView/mockData.ts | 4 +- .../RecentActivity/RecentActivity.stories.tsx | 11 +- .../RecentActivityTable/getTagType.ts | 19 ++ .../RecentActivityTable/index.tsx | 283 ++++++++++-------- .../RecentActivityTable/styles.ts | 239 +++++---------- .../RecentActivityTable/types.ts | 15 + .../RecentActivity/Toggle/index.tsx | 26 ++ .../RecentActivity/Toggle/styles.ts | 28 ++ src/components/RecentActivity/Toggle/types.ts | 29 ++ src/components/RecentActivity/index.tsx | 34 ++- src/components/RecentActivity/styles.ts | 48 +-- src/components/RecentActivity/types.ts | 36 +++ src/components/common/App/getTheme.ts | 276 ++++++++++++++--- src/components/common/NewButton/styles.ts | 2 +- src/components/common/NewButton/types.ts | 27 ++ src/components/common/Tag/Tag.stories.tsx | 69 +++++ src/components/common/Tag/index.tsx | 30 ++ src/components/common/Tag/styles.ts | 31 ++ src/components/common/Tag/types.ts | 42 +++ src/components/common/Tooltip/index.tsx | 15 +- src/components/common/Tooltip/styles.ts | 22 +- src/components/common/Tooltip/types.ts | 5 + src/styled.d.ts | 43 +-- src/typeGuards/isUndefined.ts | 1 + 34 files changed, 1086 insertions(+), 620 deletions(-) create mode 100644 src/components/RecentActivity/Badge/index.tsx create mode 100644 src/components/RecentActivity/Badge/styles.ts create mode 100644 src/components/RecentActivity/Badge/types.ts create mode 100644 src/components/RecentActivity/RecentActivityTable/getTagType.ts create mode 100644 src/components/RecentActivity/Toggle/index.tsx create mode 100644 src/components/RecentActivity/Toggle/styles.ts create mode 100644 src/components/RecentActivity/Toggle/types.ts create mode 100644 src/components/common/Tag/Tag.stories.tsx create mode 100644 src/components/common/Tag/index.tsx create mode 100644 src/components/common/Tag/styles.ts create mode 100644 src/components/common/Tag/types.ts create mode 100644 src/typeGuards/isUndefined.ts diff --git a/src/components/RecentActivity/Badge/index.tsx b/src/components/RecentActivity/Badge/index.tsx new file mode 100644 index 000000000..8b990e7eb --- /dev/null +++ b/src/components/RecentActivity/Badge/index.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import * as s from "./styles"; +import { BadgeProps } from "./types"; + +const BadgeComponent = (props: BadgeProps) => { + const size = props.size || "small"; + + return ( + + ); +}; + +export const Badge = React.memo(BadgeComponent); diff --git a/src/components/RecentActivity/Badge/styles.ts b/src/components/RecentActivity/Badge/styles.ts new file mode 100644 index 000000000..47ec05d61 --- /dev/null +++ b/src/components/RecentActivity/Badge/styles.ts @@ -0,0 +1,21 @@ +import styled from "styled-components"; +import { grayScale } from "../../common/App/getTheme"; +import { BadgeElementProps, BadgeSize } from "./types"; + +const getDimensions = (size: BadgeSize) => { + switch (size) { + case "small": + return 8; + case "large": + return 10; + } +}; + +export const Badge = styled.div` + box-sizing: border-box; + height: ${({ $size }) => getDimensions($size)}px; + width: ${({ $size }) => getDimensions($size)}px; + border-radius: 2px; + background: ${({ $backgroundColor }) => $backgroundColor || grayScale[200]}; + border: 2px solid ${({ $borderColor }) => $borderColor || grayScale[850]}; +`; diff --git a/src/components/RecentActivity/Badge/types.ts b/src/components/RecentActivity/Badge/types.ts new file mode 100644 index 000000000..dc44209b9 --- /dev/null +++ b/src/components/RecentActivity/Badge/types.ts @@ -0,0 +1,13 @@ +export type BadgeSize = "small" | "large"; + +export interface BadgeProps { + backgroundColor?: string; + borderColor?: string; + size?: BadgeSize; +} + +export interface BadgeElementProps { + $backgroundColor?: string; + $borderColor?: string; + $size: BadgeSize; +} diff --git a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/index.tsx b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/index.tsx index 0cad91f58..541dc7b9b 100644 --- a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/index.tsx +++ b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/index.tsx @@ -1,12 +1,13 @@ import { useCallback, useContext, useRef, useState } from "react"; import { ConfigContext } from "../../../common/App/ConfigContext"; -import { Badge } from "../../../common/Badge"; +import { greenScale } from "../../../common/App/getTheme"; import { KebabMenuButton } from "../../../common/KebabMenuButton"; import { NewPopover } from "../../../common/NewPopover"; import { Tooltip } from "../../../common/Tooltip"; import { DesktopIcon } from "../../../common/icons/DesktopIcon"; import { InfinityIcon } from "../../../common/icons/InfinityIcon"; import { TrashBinIcon } from "../../../common/icons/TrashBinIcon"; +import { Badge } from "../../Badge"; import { EnvironmentMenu } from "../../EnvironmentMenu"; import * as s from "./styles"; import { EnvironmentTabProps } from "./types"; @@ -81,14 +82,17 @@ export const EnvironmentTab = (props: EnvironmentTabProps) => { onBlur={handleBlur} onClick={handleClick} > - {props.environment.hasRecentActivity && ( - - - - )} {renderIcon()} - {props.environment.name} + + {props.environment.name} + {props.environment.hasRecentActivity && ( + + )} + {isMenuVisible && menuItems.length > 0 && diff --git a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/styles.ts b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/styles.ts index 331860a3b..f9344d8ee 100644 --- a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/styles.ts +++ b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/styles.ts @@ -3,74 +3,51 @@ import { ContainerProps } from "./types"; export const Container = styled.li` display: flex; - position: relative; cursor: pointer; - font-weight: ${({ $isSelected }) => ($isSelected ? 700 : 500)}; - font-size: 14px; - padding: 4px 12px; + font-size: 13px; + padding: 0 4px; user-select: none; align-items: center; gap: 4px; - color: ${({ $isPending, $isSelected, theme }) => { + box-sizing: border-box; + color: ${({ theme, $isPending, $isSelected }) => { if ($isPending) { - switch (theme.mode) { - case "light": - return "#c9ccd6"; - case "dark": - case "dark-jetbrains": - return "#5a5d63"; - } + return theme.colors.tab.text.disabled; } if ($isSelected) { - switch (theme.mode) { - case "light": - return "#494b57"; - case "dark": - return "#b9c2eb"; - case "dark-jetbrains": - return "#dfe1e5"; - } + return theme.colors.tab.text.focus; } - switch (theme.mode) { - case "light": - return "#818594"; - case "dark": - return "#7c7c94"; - case "dark-jetbrains": - return "#b4b8bf"; - } + return theme.colors.tab.text.default; }}; - border-bottom: ${({ $isSelected }) => - $isSelected ? "1px solid #5154ec" : "none"}; - - &:hover { - font-weight: 700; - ${({ theme, $isPending }) => { - let color = ""; + background: ${({ theme }) => theme.colors.tab.background.default}; + border-bottom: ${({ theme, $isSelected }) => + $isSelected ? `1px solid ${theme.colors.tab.borderBottom.focus}` : "none"}; + + &:hover, + &:focus { + color: ${({ theme, $isPending, $isSelected }) => { + if ($isPending) { + return theme.colors.tab.text.disabled; + } - if (!$isPending) { - switch (theme.mode) { - case "light": - color = "#002d61"; - break; - case "dark": - color = "#b9c2eb"; - break; - case "dark-jetbrains": - color = "#dfe1e5"; - break; - } + if ($isSelected) { + return theme.colors.tab.text.focus; } - return color ? `color: ${color};` : ""; + return theme.colors.tab.text.hover; }}; + background: ${({ theme }) => theme.colors.tab.background.hover}; + border-bottom: ${({ theme }) => + `1px solid ${theme.colors.tab.borderBottom.hover}`}; } +`; - transition-property: color, font-weight; - transition-duration: 300ms; - transition-timing-function: ease-out; +export const LabelContainer = styled.div` + display: flex; + align-items: center; + gap: 8px; `; export const Label = styled.span` @@ -79,9 +56,3 @@ export const Label = styled.span` overflow: hidden; max-width: 110px; `; - -export const BadgeContainer = styled.div` - position: absolute; - top: -1px; - left: -1px; -`; diff --git a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/types.ts b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/types.ts index 7639961bb..17dcad6c5 100644 --- a/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/types.ts +++ b/src/components/RecentActivity/EnvironmentPanel/EnvironmentTab/types.ts @@ -1,5 +1,24 @@ import { ExtendedEnvironment } from "../../types"; +export interface TabThemeColors { + text: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + background: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + borderBottom: { + hover: string; + focus: string; + }; +} + export interface EnvironmentTabProps { environment: ExtendedEnvironment; isSelected: boolean; diff --git a/src/components/RecentActivity/EnvironmentPanel/index.tsx b/src/components/RecentActivity/EnvironmentPanel/index.tsx index 26feb619b..9146c9577 100644 --- a/src/components/RecentActivity/EnvironmentPanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentPanel/index.tsx @@ -1,14 +1,11 @@ import { useEffect, useState } from "react"; import useDimensions from "react-cool-dimensions"; import { RECENT_ACTIVITY_CONTAINER_ID } from ".."; -import { IconButton } from "../../common/IconButton"; import { NewButton } from "../../common/NewButton"; import { NewPopover } from "../../common/NewPopover"; import { ChevronIcon } from "../../common/icons/ChevronIcon"; -import { DigmaLogoFlatDetailedIcon } from "../../common/icons/DigmaLogoFlatDetailedIcon"; -import { ListIcon } from "../../common/icons/ListIcon"; +import { DigmaLogoIcon } from "../../common/icons/DigmaLogoIcon"; import { PlusIcon } from "../../common/icons/PlusIcon"; -import { TableIcon } from "../../common/icons/TableIcon"; import { Direction } from "../../common/icons/types"; import { AddEnvironmentDialog } from "../AddEnvironmentDialog"; import { ExtendedEnvironment } from "../types"; @@ -42,16 +39,6 @@ export const EnvironmentPanel = (props: EnvironmentPanelProps) => { props.onEnvironmentSelect(environment); }; - const icons = { - list: ListIcon, - table: TableIcon - }; - - const handleViewModeButtonClick = () => { - const mode = props.viewMode === "table" ? "list" : "table"; - props.onViewModeChange(mode); - }; - const handleCloseAddEnvironmentDialog = () => { setIsAddEnvironmentDialogOpen(false); }; @@ -146,59 +133,54 @@ export const EnvironmentPanel = (props: EnvironmentPanelProps) => { }; return ( - - - - - - - - - {areCarouselButtonsVisible && ( - handleCarouselButtonClick("left")} - disabled={isLeftCarouselButtonDisabled} - > - - - )} - - - - {props.environments.map((environment) => ( - - ))} - - - - {areCarouselButtonsVisible && ( - handleCarouselButtonClick("right")} - disabled={isRightCarouselButtonDisabled} - > - - - )} - - - {isAddButtonVisible && renderAddButton()} - - - - + + + + + + + + + {areCarouselButtonsVisible && ( + handleCarouselButtonClick("left")} + disabled={isLeftCarouselButtonDisabled} + > + + + )} + + + + {props.environments.map((environment) => ( + + ))} + + + + {areCarouselButtonsVisible && ( + handleCarouselButtonClick("right")} + disabled={isRightCarouselButtonDisabled} + > + + + )} + + + {isAddButtonVisible && renderAddButton()} + + ); }; diff --git a/src/components/RecentActivity/EnvironmentPanel/styles.ts b/src/components/RecentActivity/EnvironmentPanel/styles.ts index db5fd02ea..55eb52f12 100644 --- a/src/components/RecentActivity/EnvironmentPanel/styles.ts +++ b/src/components/RecentActivity/EnvironmentPanel/styles.ts @@ -1,60 +1,23 @@ import styled, { keyframes } from "styled-components"; -const BORDER_RADIUS = 8; // in pixels - -const backgroundAnimation = keyframes` - 0% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } - 100% { background-position: 0% 50%; } -`; - -export const BorderContainer = styled.div` - padding: 1px; - border-radius: ${BORDER_RADIUS}px; - ${/* TODO: Change to radial gradient after cross-fading */ ""} - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "linear-gradient(90deg, #a3aaed 0%, #dde0ff 50%, #6172fe 100%)"; - case "dark": - case "dark-jetbrains": - return "linear-gradient(90deg, #2f3750 0%, #3e489b 48.96%, #7b85d7 100%)"; - } - }}; - box-shadow: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "0px 0px 5px rgb(167 176 255 / 50%)"; - case "dark": - case "dark-jetbrains": - return "0px 0px 5px rgb(167 176 255 / 25%)"; - } - }}; - ${/* TODO: Replace with cross-fading backgrounds */ ""} - background-size: 400% 400%; - animation: ${backgroundAnimation} 4s ease-in-out infinite; -`; - export const Container = styled.div` display: flex; align-items: center; width: 100%; - height: 100%; gap: 4px; - background: ${({ theme }) => { + padding: 0 12px; + height: 36px; + box-sizing: border-box; + background: ${({ theme }) => theme.colors.tabPanel.background}; + box-shadow: ${({ theme }) => { switch (theme.mode) { case "light": - return "#ebecf0"; + return "0 5px 10px 0 rgb(0 0 0 / 15%)"; case "dark": - return "#1e1e1e"; case "dark-jetbrains": - return "#393b40"; + return "0 9px 24px 0 rgb(0 0 0 / 30%)"; } }}; - padding: 5px 12px 6px; - border-radius: 8px; - position: relative; - box-sizing: border-box; `; export const EnvironmentListContainer = styled.div` @@ -63,13 +26,14 @@ export const EnvironmentListContainer = styled.div` overflow: hidden; scroll-behavior: smooth; gap: 12px; + height: 100%; `; export const EnvironmentList = styled.ul` display: flex; gap: 12px; margin: 0; - padding: 1px 0 0 1px; + padding: 0; `; export const CarouselButtonContainer = styled.div` @@ -111,6 +75,14 @@ export const LogoContainer = styled.div` animation: ${rotateAnimation} 6s ease-in-out infinite; `; +export const Divider = styled.div` + margin: 0 8px; + border-radius: 1px; + width: 1px; + height: 13px; + background: ${({ theme }) => theme.colors.tabPanel.divider}; +`; + export const ButtonsContainer = styled.div` display: flex; flex-shrink: 0; diff --git a/src/components/RecentActivity/EnvironmentPanel/types.ts b/src/components/RecentActivity/EnvironmentPanel/types.ts index 91c3d4475..94f7682a8 100644 --- a/src/components/RecentActivity/EnvironmentPanel/types.ts +++ b/src/components/RecentActivity/EnvironmentPanel/types.ts @@ -1,11 +1,9 @@ import { ExtendedEnvironment } from "../types"; export interface EnvironmentPanelProps { - viewMode: ViewMode; environments: ExtendedEnvironment[]; selectedEnvironment?: ExtendedEnvironment; onEnvironmentSelect: (environment: ExtendedEnvironment) => void; - onViewModeChange: (mode: ViewMode) => void; onEnvironmentAdd: (environment: string) => void; onEnvironmentDelete: (environment: string) => void; } diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 11cbad6d9..3db3e4e24 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -327,19 +327,23 @@ export const LiveView = (props: LiveViewProps) => { const spanName = props.data.durationData.displayName; - let data: ExtendedLiveDataRecord[] = [...props.data.liveDataRecords].map( + const data: ExtendedLiveDataRecord[] = [...props.data.liveDataRecords].map( (x) => ({ ...x, dateTimeValue: new Date(x.dateTime).valueOf() }) ); + let filteredData: ExtendedLiveDataRecord[] = data; + if (!areErrorsVisible) { - data = data.filter((x) => !x.isError); + filteredData = data.filter((x) => !x.isError); } + const dataForAxes = filteredData.length > 0 ? filteredData : data; + // Add P50 and P95 values to build the correct scale for Y axis - const YAxisData = data.concat([ + const YAxisData = dataForAxes.concat([ ...(p50 ? [ { @@ -441,7 +445,7 @@ export const LiveView = (props: LiveViewProps) => { - {props.data.liveDataRecords.length > 0 ? ( + {data.length > 0 ? ( <> { height={"100%"} > { // @ts-ignore onMouseMove={handleAreaMouseMove} onMouseLeave={handleAreaMouseLeave} + data={dataForAxes} /> )} { }} stroke={axisColor} tickFormatter={formatXAxisDate} + domain={dataForAxes.map((x) => x.dateTimeValue)} /> diff --git a/src/components/RecentActivity/LiveView/mockData.ts b/src/components/RecentActivity/LiveView/mockData.ts index 25df35bad..b765cceab 100644 --- a/src/components/RecentActivity/LiveView/mockData.ts +++ b/src/components/RecentActivity/LiveView/mockData.ts @@ -800,9 +800,9 @@ export const mockData: LiveData = { }, { duration: { - value: 8.29, + value: 18.29, unit: "ms", - raw: 8287000.0 + raw: 18287000.0 }, dateTime: "2023-05-15T20:32:22.556144Z", isError: true diff --git a/src/components/RecentActivity/RecentActivity.stories.tsx b/src/components/RecentActivity/RecentActivity.stories.tsx index 5f2e32fc9..912951071 100644 --- a/src/components/RecentActivity/RecentActivity.stories.tsx +++ b/src/components/RecentActivity/RecentActivity.stories.tsx @@ -126,9 +126,16 @@ const data: RecentActivityData = { "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", methodCodeObjectId: "" }, - lastEntrySpan: null, + lastEntrySpan: { + displayText: "PetClinicWithAgent:HTTP GET /webjars/**", + serviceName: "PetClinicWithAgent", + scopeId: "HTTP GET /webjars/**", + spanCodeObjectId: + "span:io.opentelemetry.tomcat-10.0$_$HTTP GET /webjars/**", + methodCodeObjectId: "" + }, latestTraceId: "DB80F24773E2BBE574E97960F9CB0D64", - latestTraceTimestamp: "2023-10-27T13:47:21.794Z", + latestTraceTimestamp: "2023-12-11T15:40:21.794Z", latestTraceDuration: { value: 3.9, unit: "ms", diff --git a/src/components/RecentActivity/RecentActivityTable/getTagType.ts b/src/components/RecentActivity/RecentActivityTable/getTagType.ts new file mode 100644 index 000000000..753aa6be7 --- /dev/null +++ b/src/components/RecentActivity/RecentActivityTable/getTagType.ts @@ -0,0 +1,19 @@ +import { TagType } from "../../common/Tag/types"; + +export const getTagType = (importance: number): TagType | undefined => { + if (importance === 0) { + return undefined; + } + + if (importance < 3) { + return "highSeverity"; + } + if (importance < 5) { + return "mediumSeverity"; + } + if (importance < 7) { + return "lowSeverity"; + } + + return "success"; +}; diff --git a/src/components/RecentActivity/RecentActivityTable/index.tsx b/src/components/RecentActivity/RecentActivityTable/index.tsx index 85b07ecbb..e120d7ddf 100644 --- a/src/components/RecentActivity/RecentActivityTable/index.tsx +++ b/src/components/RecentActivity/RecentActivityTable/index.tsx @@ -5,21 +5,22 @@ import { useReactTable } from "@tanstack/react-table"; import { useMemo } from "react"; -import { useTheme } from "styled-components"; import { Duration } from "../../../globals"; import { isNumber } from "../../../typeGuards/isNumber"; import { formatTimeDistance } from "../../../utils/formatTimeDistance"; -import { getInsightImportanceColor } from "../../../utils/getInsightImportanceColor"; import { getInsightTypeInfo } from "../../../utils/getInsightTypeInfo"; import { getInsightTypeOrderPriority } from "../../../utils/getInsightTypeOrderPriority"; -import { Badge } from "../../common/Badge"; -import { Button } from "../../common/Button"; +import { greenScale } from "../../common/App/getTheme"; +import { NewButton } from "../../common/NewButton"; +import { Tag } from "../../common/Tag"; import { Tooltip } from "../../common/Tooltip"; import { CrosshairIcon } from "../../common/icons/CrosshairIcon"; +import { Badge } from "../Badge"; import { ViewMode } from "../EnvironmentPanel/types"; import { ActivityEntry, EntrySpan, SlimInsight } from "../types"; +import { getTagType } from "./getTagType"; import * as s from "./styles"; -import { RecentActivityTableProps } from "./types"; +import { ColumnMeta, RecentActivityTableProps } from "./types"; const columnHelper = createColumnHelper(); @@ -34,9 +35,70 @@ export const isRecent = (entry: ActivityEntry): boolean => { ); }; -export const RecentActivityTable = (props: RecentActivityTableProps) => { - const theme = useTheme(); +const renderBadge = () => ( + + + +); + +const renderTimeDistance = (timestamp: string, viewMode: ViewMode) => { + const title = new Date(timestamp).toString(); + const timeDistanceString = formatTimeDistance(timestamp, { + format: "short", + withDescriptiveWords: false + }); + + return viewMode === "table" ? ( + + ) : ( + + + {timeDistanceString.replace(" ", "")} + + ago + + ); +}; + +const renderDuration = (duration: Duration, viewMode: ViewMode) => + viewMode === "table" ? ( + + ) : ( + + {duration.value} + {duration.unit} + + ); +const renderInsights = (insights: SlimInsight[]) => { + const sortedInsights = [...insights].sort( + (a, b) => + a.importance - b.importance || + getInsightTypeOrderPriority(a.type) - getInsightTypeOrderPriority(b.type) + ); + + return ( + + {sortedInsights + .map((x) => { + const insightTypeInfo = getInsightTypeInfo(x.type); + const tagType = getTagType(x.importance); + + return insightTypeInfo ? ( + + ) : null; + }) + .filter(Boolean)} + + ); +}; + +export const RecentActivityTable = (props: RecentActivityTableProps) => { const handleSpanLinkClick = (span: EntrySpan) => { props.onSpanLinkClick(span); }; @@ -46,117 +108,52 @@ export const RecentActivityTable = (props: RecentActivityTableProps) => { }; const renderSpanLink = (span: EntrySpan) => ( - { - handleSpanLinkClick(span); - }} - > - {span.displayText} - + + { + handleSpanLinkClick(span); + }} + > + {span.displayText} + + ); const renderSpanLinks = (entry: ActivityEntry) => ( - + {renderSpanLink(entry.firstEntrySpan)} {entry.lastEntrySpan && <> to {renderSpanLink(entry.lastEntrySpan)}} - + ); - const renderTimeDistance = (timestamp: string, viewMode: ViewMode) => { - const title = new Date(timestamp).toString(); - const timeDistanceString = formatTimeDistance(timestamp, { - format: "short", - withDescriptiveWords: false - }).replace(" ", ""); - - return viewMode === "table" ? ( - - - {timeDistanceString} - - - ago - - ) : ( - - - {timeDistanceString} - - ago - - ); - }; - - const renderDuration = (duration: Duration, viewMode: ViewMode) => - viewMode === "table" ? ( - - {duration.value} - {duration.unit} - - ) : ( - - {duration.value} - {duration.unit} - - ); - - const renderInsights = (insights: SlimInsight[]) => { - const sortedInsights = [...insights].sort( - (a, b) => - a.importance - b.importance || - getInsightTypeOrderPriority(a.type) - - getInsightTypeOrderPriority(b.type) - ); - - return ( - - {sortedInsights - .map((x) => { - const insightTypeInfo = getInsightTypeInfo(x.type); - const iconColor = getInsightImportanceColor(x.importance, theme); - - return insightTypeInfo ? ( - - - - - - ) : null; - }) - .filter(Boolean)} - - ); - }; - const renderTraceButton = (entry: ActivityEntry) => ( - - - + +
+ { + handleTraceButtonClick(entry.latestTraceId, entry.firstEntrySpan); + }} + icon={CrosshairIcon} + buttonType={"secondary"} + /> +
+
); const columns = [ columnHelper.accessor((row) => row, { id: "recentActivity", - header: "", + header: "Asset", + meta: { + width: "60%", + minWidth: 60 + }, cell: (info) => { const entry = info.getValue(); return ( <> - {isRecent(entry) && ( - - - - )} + {isRecent(entry) && renderBadge()} {renderSpanLinks(entry)} ); @@ -164,21 +161,37 @@ export const RecentActivityTable = (props: RecentActivityTableProps) => { }), columnHelper.accessor("latestTraceTimestamp", { header: "Executed", + meta: { + width: "10%", + minWidth: 100 + }, cell: (info) => <>{renderTimeDistance(info.getValue(), props.viewMode)} }), columnHelper.accessor("latestTraceDuration", { header: "Duration", + meta: { + width: "10%", + minWidth: 100 + }, cell: (info) => renderDuration(info.getValue(), props.viewMode) }), columnHelper.accessor("slimAggregatedInsights", { header: "Insights", + meta: { + width: "15%", + minWidth: 60 + }, cell: (info) => renderInsights(info.getValue()) }), ...(props.isTraceButtonVisible ? [ columnHelper.accessor((row) => row, { id: "latestTraceId", - header: "", + header: "Actions", + meta: { + width: "5%", + minWidth: 55 + }, cell: (info) => renderTraceButton(info.getValue()) }) ] @@ -203,30 +216,50 @@ export const RecentActivityTable = (props: RecentActivityTableProps) => { return props.viewMode === "table" ? ( - + {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - + + {headerGroup.headers.map((header) => { + const meta = header.column.columnDef.meta as ColumnMeta; + + return ( + + {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())} - - ))} + + {row.getVisibleCells().map((cell) => { + const meta = cell.column.columnDef.meta as ColumnMeta; + + return ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ); + })} ))} @@ -235,12 +268,8 @@ export const RecentActivityTable = (props: RecentActivityTableProps) => { {sortedData.map((entry, i) => ( - - {isRecent(entry) && ( - - - - )} + + {isRecent(entry) && renderBadge()} {renderTimeDistance(entry.latestTraceTimestamp, props.viewMode)} {renderSpanLinks(entry)} {renderDuration(entry.latestTraceDuration, props.viewMode)} diff --git a/src/components/RecentActivity/RecentActivityTable/styles.ts b/src/components/RecentActivity/RecentActivityTable/styles.ts index 16b4261a8..d98d1e845 100644 --- a/src/components/RecentActivity/RecentActivityTable/styles.ts +++ b/src/components/RecentActivity/RecentActivityTable/styles.ts @@ -1,13 +1,11 @@ import styled from "styled-components"; -import { grayScale } from "../../common/App/getTheme"; -import { getCodeFont } from "../../common/App/styles"; import { Link } from "../../common/Link"; +import { ListItemProps, TableBodyRowProps, TableHeadProps } from "./types"; -export const TABLE_BODY_ROW_VERTICAL_SPACING = 4; // in pixels; - -export const Table = styled.table` +export const Table = styled.div` width: 100%; - border-spacing: 0; + display: flex; + flex-direction: column; font-size: 14px; color: ${({ theme }) => { switch (theme.mode) { @@ -21,134 +19,79 @@ export const Table = styled.table` }}; `; -export const TableHead = styled.thead<{ offset: number }>` +export const TableHead = styled.div` position: sticky; - top: ${({ offset }) => offset - 1}px; + display: flex; + top: ${({ $offset }) => $offset - 1}px; z-index: 1; font-size: 14px; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#818594"; - case "dark": - return "#b9c2eb"; - case "dark-jetbrains": - return "#b4b8bf"; - } - }}; - box-shadow: -4px 0 - ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return grayScale[1200]; - } - }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return grayScale[1200]; - } - }}; + color: ${({ theme }) => theme.colors.recentActivity.table.header.text}; + box-shadow: -4px 0 ${({ theme }) => theme.colors.recentActivity.background}; + background: ${({ theme }) => theme.colors.recentActivity.background}; + padding-bottom: 8px; `; -export const TableHeaderCell = styled.th` - padding: 4px 0; - font-weight: 400; - text-align: start; - - &:first-child { - padding-left: 12px; - } +export const TableHeadRow = styled.div` + display: flex; + width: 100%; + gap: 20px; `; -export const TableBody = styled.tbody` - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#ebecf0"; - case "dark": - return "#1e1e1e"; - case "dark-jetbrains": - return "#393b40"; - } - }}; - - &::before { - content: ""; - display: block; - height: 2px; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return grayScale[1200]; - } - }}; - } - - & tr:first-child td:first-child { - border-top-left-radius: 12px; - } - - & tr:first-child td:last-child { - border-top-right-radius: 12px; - } +export const TableHeaderCell = styled.div` + text-align: start; + overflow: hidden; - & tr:last-child td:first-child { - border-bottom-left-radius: 12px; + &:first-child { + padding-left: 8px; } - & tr:last-child td:last-child { - border-bottom-right-radius: 12px; + &:last-child { + padding-right: 8px; } `; -export const TableBodyRow = styled.tr` - position: relative; - height: ${42 + TABLE_BODY_ROW_VERTICAL_SPACING}px; - - &:not(:last-child) > td { - border-bottom: ${TABLE_BODY_ROW_VERTICAL_SPACING}px solid - ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return "#2b2d30"; - } - }}; - } +export const TableBody = styled.div` + display: flex; + flex-direction: column; + gap: 8px; `; -export const TableBodyCell = styled.td` - padding: 0; +export const TableBodyRow = styled.div` + display: flex; + position: relative; + height: 36px; + border-radius: 4px; + border: 1px solid + ${({ theme, $isRecent }) => + $isRecent + ? theme.colors.recentActivity.table.row.new.border + : theme.colors.recentActivity.table.row.default.border}; + gap: 20px; + background: ${({ theme, $isRecent }) => + $isRecent + ? theme.colors.recentActivity.table.row.new.background + : theme.colors.recentActivity.table.row.default.background}; + box-sizing: border-box; +`; + +export const TableBodyCell = styled.div` + display: flex; + align-items: center; + overflow: hidden; &:first-child { - padding-left: 12px; + padding-left: 8px; } &:last-child { - padding-right: 12px; + padding-right: 8px; } `; export const BadgeContainer = styled.div` position: absolute; - top: -1px; - left: -1px; + margin: auto; + left: -4px; `; export const TimeDistanceContainer = styled.span` @@ -168,28 +111,8 @@ export const InsightsContainer = styled.span` gap: 4px; `; -export const InsightIconContainer = styled.span` - display: flex; - justify-content: center; - align-items: center; - width: 24px; - height: 24px; - flex-shrink: 0; - border-radius: 4px; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#e9eef4"; - case "dark": - case "dark-jetbrains": - return "#2e2e2e"; - } - }}; -`; - export const TraceButtonContainer = styled.div` display: flex; - justify-content: flex-end; `; export const Suffix = styled.span` @@ -229,19 +152,11 @@ export const List = styled.ul` return "#c6c6c6"; } }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#ebecf0"; - case "dark": - return "#1e1e1e"; - case "dark-jetbrains": - return "#393b40"; - } - }}; + border: 1px solid + ${({ theme }) => theme.colors.recentActivity.table.row.default.border}; `; -export const ListItem = styled.li` +export const ListItem = styled.li` position: relative; font-size: 14px; padding: 0 12px; @@ -250,16 +165,20 @@ export const ListItem = styled.li` align-items: center; gap: 8px; font-weight: 500; -`; + background: ${({ theme, $isRecent }) => + $isRecent + ? theme.colors.recentActivity.table.row.new.background + : theme.colors.recentActivity.table.row.default.background}; -export const ListBadgeContainer = styled.div` - position: absolute; - display: flex; - top: 0; - bottom: 0; - left: -4px; - margin: auto; - height: fit-content; + &:first-child { + border-top-left-radius: 11px; + border-top-right-radius: 11px; + } + + &:last-child { + border-bottom-left-radius: 11px; + border-bottom-right-radius: 11px; + } `; export const ListInsightsContainer = styled.div` @@ -271,16 +190,16 @@ export const ListSuffix = styled(Suffix)` font-size: 14px; `; -export const SpanLink = styled(Link)` - ${({ theme }) => getCodeFont(theme.codeFont)} +export const SpanLinksContainer = styled.span` + overflow: hidden; + display: flex; + gap: 4px; +`; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#426dda"; - case "dark": - case "dark-jetbrains": - return "#7891d0"; - } - }}; +export const SpanLink = styled(Link)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; + color: ${({ theme }) => theme.colors.recentActivity.table.row.default.link}; `; diff --git a/src/components/RecentActivity/RecentActivityTable/types.ts b/src/components/RecentActivity/RecentActivityTable/types.ts index d4c636f5f..2a846c91a 100644 --- a/src/components/RecentActivity/RecentActivityTable/types.ts +++ b/src/components/RecentActivity/RecentActivityTable/types.ts @@ -9,3 +9,18 @@ export interface RecentActivityTableProps { isTraceButtonVisible: boolean; headerHeight: number; } + +export interface ColumnMeta { + width: string; + minWidth: number; +} + +export interface TableHeadProps { + $offset: number; +} + +export interface TableBodyRowProps { + $isRecent: boolean; +} + +export type ListItemProps = TableBodyRowProps; diff --git a/src/components/RecentActivity/Toggle/index.tsx b/src/components/RecentActivity/Toggle/index.tsx new file mode 100644 index 000000000..668037dad --- /dev/null +++ b/src/components/RecentActivity/Toggle/index.tsx @@ -0,0 +1,26 @@ +import * as s from "./styles"; +import { ToggleProps } from "./types"; + +export const Toggle = (props: ToggleProps) => { + const handleValueChange = (value: T) => { + props.onChange(value); + }; + + return ( + + {props.options.map((option) => { + const isSelected = option.value === props.value; + + return ( + handleValueChange(option.value)} + > + + + ); + })} + + ); +}; diff --git a/src/components/RecentActivity/Toggle/styles.ts b/src/components/RecentActivity/Toggle/styles.ts new file mode 100644 index 000000000..bad2647f1 --- /dev/null +++ b/src/components/RecentActivity/Toggle/styles.ts @@ -0,0 +1,28 @@ +import styled from "styled-components"; +import { ToggleOptionButtonProps } from "./types"; + +export const Container = styled.div` + display: flex; + border-radius: 4px; + padding: 4px; + gap: 4px; + border: 1px solid ${({ theme }) => theme.colors.toggle.border}; + background: ${({ theme }) => theme.colors.toggle.background}; +`; + +export const ToggleOptionButton = styled.button` + display: flex; + border: none; + outline: none; + padding: 2px; + border-radius: 2px; + cursor: pointer; + background: ${({ theme, $selected }) => + $selected + ? theme.colors.toggle.option.selected.background + : theme.colors.toggle.option.default.background}; + color: ${({ theme, $selected }) => + $selected + ? theme.colors.toggle.option.selected.icon + : theme.colors.toggle.option.default.icon}; +`; diff --git a/src/components/RecentActivity/Toggle/types.ts b/src/components/RecentActivity/Toggle/types.ts new file mode 100644 index 000000000..135f391f9 --- /dev/null +++ b/src/components/RecentActivity/Toggle/types.ts @@ -0,0 +1,29 @@ +import { ComponentType } from "react"; +import { IconProps } from "../../common/icons/types"; + +export interface ToggleThemeColors { + background: string; + border: string; + option: { + default: { + background: string; + text: string; + icon: string; + }; + selected: { + background: string; + text: string; + icon: string; + }; + }; +} + +export interface ToggleProps { + options: { value: T; icon: ComponentType }[]; + value: T; + onChange: (value: T) => void; +} + +export interface ToggleOptionButtonProps { + $selected: boolean; +} diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 6d251c26d..f8e960133 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -11,6 +11,8 @@ import { sendTrackingEvent } from "../../utils/sendTrackingEvent"; import { ConfigContext } from "../common/App/ConfigContext"; import { CursorFollower } from "../common/CursorFollower"; import { DigmaLogoFlatIcon } from "../common/icons/DigmaLogoFlatIcon"; +import { ListIcon } from "../common/icons/ListIcon"; +import { TableIcon } from "../common/icons/TableIcon"; import { DeleteEnvironmentConfirmation } from "./DeleteEnvironmentConfirmation"; import { EnvironmentInstructionsPanel } from "./EnvironmentInstructionsPanel"; import { EnvironmentPanel } from "./EnvironmentPanel"; @@ -23,6 +25,7 @@ import { RecentActivityTable, isRecent } from "./RecentActivityTable"; import { RegistrationPanel } from "./RegistrationPanel"; import { RegistrationFormValues } from "./RegistrationPanel/types"; import { SetupOrgDigmaPanel } from "./SetupOrgDigmaPanel"; +import { Toggle } from "./Toggle"; import { actions } from "./actions"; import * as s from "./styles"; import { @@ -30,11 +33,23 @@ import { EnvironmentType, ExtendedEnvironment, RecentActivityData, - RecentActivityProps + RecentActivityProps, + ViewModeOption } from "./types"; export const RECENT_ACTIVITY_CONTAINER_ID = "recent-activity"; +const viewModeOptions: ViewModeOption[] = [ + { + value: "table", + icon: TableIcon + }, + { + value: "list", + icon: ListIcon + } +]; + const handleTroubleshootButtonClick = () => { sendTrackingEvent(globalTrackingEvents.TROUBLESHOOTING_LINK_CLICKED, { origin: "recent activity" @@ -398,21 +413,24 @@ export const RecentActivity = (props: RecentActivityProps) => { - {!selectedEnvironment?.isPending && ( - <> + + {!selectedEnvironment?.isPending && ( Recent Activity + - - )} - {!config.isObservabilityEnabled && } + )} + {!config.isObservabilityEnabled && } + {renderContent()} diff --git a/src/components/RecentActivity/styles.ts b/src/components/RecentActivity/styles.ts index b2811bd19..30b06e9a6 100644 --- a/src/components/RecentActivity/styles.ts +++ b/src/components/RecentActivity/styles.ts @@ -1,21 +1,13 @@ import styled from "styled-components"; -import { grayScale } from "../common/App/getTheme"; import { LAYERS } from "../common/App/styles"; import { Button } from "../common/Button"; +const RECENT_ACTIVITY_MIN_WIDTH = 550; // in pixels + export const Container = styled.div` height: 100%; position: relative; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return grayScale[1200]; - } - }}; + background: ${({ theme }) => theme.colors.recentActivity.background}; .sash { --sash-size: 12px; @@ -73,44 +65,34 @@ export const RecentActivityContainer = styled.div` export const RecentActivityHeader = styled.div` box-sizing: border-box; - padding: 12px 12px 8px; z-index: 1; position: sticky; top: 0; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#f7f8fa"; - case "dark": - return "#0f0f0f"; - case "dark-jetbrains": - return grayScale[1200]; - } - }}; + display: flex; + flex-direction: column; + min-width: ${RECENT_ACTIVITY_MIN_WIDTH}px; + background: ${({ theme }) => theme.colors.recentActivity.background}; +`; + +export const RecentActivityToolbarContainer = styled.div` display: flex; flex-direction: column; gap: 8px; + padding: 16px 12px 20px; `; export const RecentActivityToolbar = styled.div` display: flex; justify-content: space-between; - padding-top: 8px; - font-weight: 400; + font-weight: 500; font-size: 14px; - color: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#494b57"; - case "dark": - case "dark-jetbrains": - return "#dfe1e5"; - } - }}; + color: ${({ theme }) => theme.colors.recentActivity.header.text}; `; export const RecentActivityContentContainer = styled.div` padding: 0 12px 12px; + box-sizing: border-box; + min-width: ${RECENT_ACTIVITY_MIN_WIDTH}px; `; export const NoDataContainer = styled.div` diff --git a/src/components/RecentActivity/types.ts b/src/components/RecentActivity/types.ts index c5f572326..656801756 100644 --- a/src/components/RecentActivity/types.ts +++ b/src/components/RecentActivity/types.ts @@ -1,6 +1,38 @@ +import { ComponentType } from "react"; import { Duration } from "../../globals"; +import { IconProps } from "../common/icons/types"; +import { ViewMode } from "./EnvironmentPanel/types"; import { LiveData } from "./LiveView/types"; +export interface RecentActivityThemeColors { + background: string; + header: { + text: string; + }; + table: { + header: { + text: string; + }; + row: { + default: { + border: string; + background: string; + link: string; + }; + new: { + border: string; + background: string; + link: string; + }; + }; + }; +} + +export interface ViewModeOption { + value: ViewMode; + icon: ComponentType; +} + export interface EntrySpan { displayText: string; serviceName: string; @@ -56,3 +88,7 @@ export interface RecentActivityProps { export interface SetIsJaegerData { isJaegerEnabled: boolean; } + +export interface ViewModeOptionProps { + $selected: boolean; +} diff --git a/src/components/common/App/getTheme.ts b/src/components/common/App/getTheme.ts index f4d3672b4..c42d2e84f 100644 --- a/src/components/common/App/getTheme.ts +++ b/src/components/common/App/getTheme.ts @@ -31,6 +31,12 @@ export const primaryScale = { 700: "#28293e" }; +export const blueScale = { + 300: "#58a5ff", + 400: "#2c344f", + 500: "#2d87da" +}; + export const redScale = { 200: "faf1f3", 300: "#ff7e7e", @@ -38,20 +44,27 @@ export const redScale = { 500: "#da2d5f" }; +export const orangeScale = { + 300: "#ff8a58", + 400: "#3c2c23", + 500: "#da802d" +}; + export const greenScale = { 300: "#6ebd9c", 400: "#233332", 500: "#2ddabb" }; -const lightThemeColors: ThemeColors = { +const darkThemeColors: ThemeColors = { + icon: grayScale[200], button: { primary: { background: { default: primaryScale[300], hover: primaryScale[200], focus: primaryScale[200], - disabled: grayScale[200] + disabled: grayScale[700] }, icon: { default: grayScale[200], @@ -68,55 +81,143 @@ const lightThemeColors: ThemeColors = { }, secondary: { background: { - default: grayScale[50], - hover: grayScale[50], - focus: grayScale[50], + default: primaryScale[700], + hover: primaryScale[700], + focus: primaryScale[700], disabled: "transparent" }, border: { default: primaryScale[300], hover: primaryScale[200], focus: primaryScale[200], - disabled: grayScale[200] + disabled: grayScale[700] }, icon: { - default: primaryScale[300], - hover: primaryScale[300], - focus: primaryScale[300], - disabled: grayScale[400] + default: grayScale[200], + hover: grayScale[200], + focus: grayScale[200], + disabled: grayScale[500] }, text: { - default: grayScale[900], - hover: grayScale[900], - focus: grayScale[900], - disabled: grayScale[400] + default: grayScale[0], + hover: grayScale[0], + focus: grayScale[0], + disabled: grayScale[500] } }, tertiary: { icon: { - default: grayScale[800], - hover: primaryScale[300], - focus: primaryScale[300], - disabled: grayScale[400] + default: grayScale[200], + hover: primaryScale[200], + focus: primaryScale[200], + disabled: grayScale[700] }, text: { - default: grayScale[900], - hover: primaryScale[300], - focus: primaryScale[300], - disabled: grayScale[400] + default: grayScale[0], + hover: primaryScale[200], + focus: primaryScale[200], + disabled: grayScale[700] + } + } + }, + tab: { + text: { + default: grayScale[400], + hover: grayScale[400], + focus: grayScale[200], + disabled: grayScale[700] + }, + background: { + default: "transparent", + hover: grayScale[800], + focus: "transparent", + disabled: "transparent" + }, + borderBottom: { + hover: primaryScale[300], + focus: primaryScale[300] + } + }, + tabPanel: { + background: grayScale[1100], + divider: grayScale[800] + }, + tag: { + default: { + background: grayScale[850], + text: grayScale[200] + }, + highSeverity: { + background: redScale[400], + text: redScale[300] + }, + mediumSeverity: { + background: orangeScale[400], + text: orangeScale[300] + }, + lowSeverity: { + background: blueScale[400], + text: blueScale[300] + }, + success: { + background: greenScale[400], + text: greenScale[300] + } + }, + toggle: { + background: grayScale[1000], + border: grayScale[850], + option: { + default: { + background: "transparent", + icon: grayScale[200], + text: grayScale[100] + }, + selected: { + background: primaryScale[300], + icon: grayScale[200], + text: grayScale[100] + } + } + }, + recentActivity: { + background: grayScale[1200], + header: { + text: grayScale[100] + }, + table: { + header: { + text: grayScale[400] + }, + row: { + default: { + border: grayScale[850], + background: grayScale[1100], + link: primaryScale[100] + }, + new: { + border: grayScale[850], + background: grayScale[900], + link: primaryScale[100] + } } } + }, + tooltip: { + background: grayScale[800], + text: grayScale[100] } }; -const darkThemeColors: ThemeColors = { +const lightThemeColors: ThemeColors = { + icon: grayScale[800], button: { primary: { background: { default: primaryScale[300], hover: primaryScale[200], focus: primaryScale[200], - disabled: grayScale[700] + disabled: grayScale[200] }, icon: { default: grayScale[200], @@ -133,44 +234,131 @@ const darkThemeColors: ThemeColors = { }, secondary: { background: { - default: primaryScale[700], - hover: primaryScale[700], - focus: primaryScale[700], + default: grayScale[50], + hover: grayScale[50], + focus: grayScale[50], disabled: "transparent" }, border: { default: primaryScale[300], hover: primaryScale[200], focus: primaryScale[200], - disabled: grayScale[700] + disabled: grayScale[200] }, icon: { - default: grayScale[200], - hover: grayScale[200], - focus: grayScale[200], - disabled: grayScale[500] + default: primaryScale[300], + hover: primaryScale[300], + focus: primaryScale[300], + disabled: grayScale[400] }, text: { - default: grayScale[0], - hover: grayScale[0], - focus: grayScale[0], - disabled: grayScale[500] + default: grayScale[900], + hover: grayScale[900], + focus: grayScale[900], + disabled: grayScale[400] } }, tertiary: { icon: { - default: grayScale[200], - hover: primaryScale[200], - focus: primaryScale[200], - disabled: grayScale[700] + default: grayScale[800], + hover: primaryScale[300], + focus: primaryScale[300], + disabled: grayScale[400] }, text: { - default: grayScale[0], - hover: primaryScale[200], - focus: primaryScale[200], - disabled: grayScale[700] + default: grayScale[900], + hover: primaryScale[300], + focus: primaryScale[300], + disabled: grayScale[400] + } + } + }, + tab: { + text: { + default: grayScale[600], + hover: grayScale[600], + focus: grayScale[900], + disabled: grayScale[300] + }, + background: { + default: "transparent", + hover: grayScale[150], + focus: "transparent", + disabled: "transparent" + }, + borderBottom: { + hover: primaryScale[300], + focus: primaryScale[300] + } + }, + tabPanel: { + background: grayScale[50], + divider: grayScale[300] + }, + tag: { + default: { + background: grayScale[200], + text: grayScale[800] + }, + highSeverity: { + background: redScale[500], + text: grayScale[0] + }, + mediumSeverity: { + background: orangeScale[500], + text: grayScale[0] + }, + lowSeverity: { + background: blueScale[500], + text: grayScale[0] + }, + success: { + background: greenScale[500], + text: grayScale[0] + } + }, + toggle: { + background: grayScale[100], + border: grayScale[200], + option: { + default: { + background: "transparent", + icon: grayScale[800], + text: grayScale[700] + }, + selected: { + background: primaryScale[300], + icon: grayScale[200], + text: grayScale[100] + } + } + }, + recentActivity: { + background: grayScale[0], + header: { + text: grayScale[900] + }, + table: { + header: { + text: grayScale[600] + }, + row: { + default: { + border: grayScale[200], + background: grayScale[50], + link: primaryScale[300] + }, + new: { + border: grayScale[200], + background: grayScale[150], + link: primaryScale[300] + } } } + }, + tooltip: { + background: grayScale[100], + text: grayScale[900] } }; diff --git a/src/components/common/NewButton/styles.ts b/src/components/common/NewButton/styles.ts index b71ce3974..6209c6203 100644 --- a/src/components/common/NewButton/styles.ts +++ b/src/components/common/NewButton/styles.ts @@ -5,7 +5,7 @@ import { ButtonElementProps, ButtonType, LabelProps } from "./types"; const getButtonStyles = ( theme: DefaultTheme, type: ButtonType, - state: "default" | "hover" | "hover" | "focus" | "disabled" + state: "default" | "hover" | "focus" | "disabled" ) => { const backgroundColor = theme.colors.button[type].background; const borderColor = theme.colors.button[type].border; diff --git a/src/components/common/NewButton/types.ts b/src/components/common/NewButton/types.ts index b8b6c1655..9e8fce05a 100644 --- a/src/components/common/NewButton/types.ts +++ b/src/components/common/NewButton/types.ts @@ -1,6 +1,33 @@ import { ButtonHTMLAttributes } from "react"; import { IconProps } from "../../common/icons/types"; +export interface ButtonThemeColors { + background?: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + border?: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + icon: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + text: { + default: string; + hover: string; + focus: string; + disabled: string; + }; +} + export type ButtonType = "primary" | "secondary" | "tertiary"; export type ButtonSize = "small" | "large"; diff --git a/src/components/common/Tag/Tag.stories.tsx b/src/components/common/Tag/Tag.stories.tsx new file mode 100644 index 000000000..2a381c2e9 --- /dev/null +++ b/src/components/common/Tag/Tag.stories.tsx @@ -0,0 +1,69 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Tag } from "."; +import { BottleneckIcon } from "../icons/BottleneckIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "common/Tag", + component: Tag, + 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: { + icon: BottleneckIcon, + value: "Input" + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/2kxQzKJFUAOUcHXvSZrMQy/Digma-Design-System-2.1?node-id=32%3A17846" + } + } +}; + +export const HighSeverity: Story = { + args: { + type: "highSeverity", + value: "Input" + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/2kxQzKJFUAOUcHXvSZrMQy/Digma-Design-System-2.1?node-id=32%3A17846" + } + } +}; + +export const MediumSeverity: Story = { + args: { + type: "mediumSeverity", + value: "Input" + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/2kxQzKJFUAOUcHXvSZrMQy/Digma-Design-System-2.1?node-id=32%3A17846" + } + } +}; + +export const LowSeverity: Story = { + args: { + type: "lowSeverity", + value: "Input" + }, + parameters: { + design: { + type: "figma", + url: "https://www.figma.com/file/2kxQzKJFUAOUcHXvSZrMQy/Digma-Design-System-2.1?node-id=32%3A17846" + } + } +}; diff --git a/src/components/common/Tag/index.tsx b/src/components/common/Tag/index.tsx new file mode 100644 index 000000000..be0b888ec --- /dev/null +++ b/src/components/common/Tag/index.tsx @@ -0,0 +1,30 @@ +import { isString } from "../../../typeGuards/isString"; +import { isUndefined } from "../../../typeGuards/isUndefined"; +import { Tooltip } from "../Tooltip"; +import * as s from "./styles"; +import { TagProps } from "./types"; + +const renderValue = (value: TagProps["value"]) => { + switch (typeof value) { + case "string": + return {value}; + case "number": + return value; + default: + return null; + } +}; + +export const Tag = (props: TagProps) => { + const title = isString(props.title) ? props.title : props.value; + return ( + + + {props.icon && } + {!isUndefined(props.value) && ( + {renderValue(props.value)} + )} + + + ); +}; diff --git a/src/components/common/Tag/styles.ts b/src/components/common/Tag/styles.ts new file mode 100644 index 000000000..b624e4220 --- /dev/null +++ b/src/components/common/Tag/styles.ts @@ -0,0 +1,31 @@ +import styled from "styled-components"; +import { ContainerProps } from "./types"; + +export const Container = styled.div` + font-size: 14px; + font-weight: normal; + display: flex; + padding: 4px; + align-items: center; + min-width: 16px; + justify-content: center; + gap: 4px; + border-radius: 4px; + max-width: fit-content; + color: ${({ theme, $type }) => + $type ? theme.colors.tag[$type].text : theme.colors.tag.default.text}; + background: ${({ theme, $type }) => + $type + ? theme.colors.tag[$type].background + : theme.colors.tag.default.background}; +`; + +export const ValueContainer = styled.span` + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +`; + +export const TextContainer = styled.span` + padding: 0 4px; +`; diff --git a/src/components/common/Tag/types.ts b/src/components/common/Tag/types.ts new file mode 100644 index 000000000..fb1d77412 --- /dev/null +++ b/src/components/common/Tag/types.ts @@ -0,0 +1,42 @@ +import { MemoExoticComponent } from "react"; +import { IconProps } from "../icons/types"; + +export type TagType = + | "highSeverity" + | "mediumSeverity" + | "lowSeverity" + | "success"; + +export interface TagThemeColors { + default: { + text: string; + background: string; + }; + highSeverity: { + text: string; + background: string; + }; + mediumSeverity: { + text: string; + background: string; + }; + lowSeverity: { + text: string; + background: string; + }; + success: { + text: string; + background: string; + }; +} + +export interface TagProps { + icon?: MemoExoticComponent<(props: IconProps) => JSX.Element>; + value?: string | number; + type?: TagType; + title?: string; +} + +export interface ContainerProps { + $type?: TagType; +} diff --git a/src/components/common/Tooltip/index.tsx b/src/components/common/Tooltip/index.tsx index de830d168..e9c83fcb0 100644 --- a/src/components/common/Tooltip/index.tsx +++ b/src/components/common/Tooltip/index.tsx @@ -11,7 +11,7 @@ import { useInteractions } from "@floating-ui/react"; import { Children, cloneElement, useRef, useState } from "react"; -import { DefaultTheme, useTheme } from "styled-components"; +import { useTheme } from "styled-components"; import * as s from "./styles"; import { TooltipProps } from "./types"; @@ -50,22 +50,11 @@ const getArrowStyles = (placement: Placement) => { } }; -const getArrowColor = (theme: DefaultTheme) => { - switch (theme.mode) { - case "light": - return "#fbfdff"; - case "dark": - case "dark-jetbrains": - return "#2e2e2e"; - } -}; - export const Tooltip = (props: TooltipProps) => { const [isOpen, setIsOpen] = useState(false); const arrowRef = useRef(null); const theme = useTheme(); - const arrowColor = getArrowColor(theme); const placement = props.placement || "top"; @@ -107,7 +96,7 @@ export const Tooltip = (props: TooltipProps) => { { - switch (theme.mode) { - case "light": - return "#828797"; - case "dark": - case "dark-jetbrains": - return "#9b9b9b"; - } - }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#fbfdff"; - case "dark": - case "dark-jetbrains": - return "#2e2e2e"; - } - }}; + color: ${({ theme }) => theme.colors.tooltip.text}; + background: ${({ theme }) => theme.colors.tooltip.background}; `; diff --git a/src/components/common/Tooltip/types.ts b/src/components/common/Tooltip/types.ts index 98495ccc4..be5896292 100644 --- a/src/components/common/Tooltip/types.ts +++ b/src/components/common/Tooltip/types.ts @@ -1,6 +1,11 @@ import { Placement } from "@floating-ui/react"; import { ReactElement, ReactNode } from "react"; +export interface TooltipThemeColors { + background: string; + text: string; +} + export interface TooltipProps { children: ReactElement; title: ReactNode; diff --git a/src/styled.d.ts b/src/styled.d.ts index bbb56a759..f9f34a521 100644 --- a/src/styled.d.ts +++ b/src/styled.d.ts @@ -1,39 +1,28 @@ import "styled-components"; +import { TagThemeColors } from "./components/Insights/common/Tag/types"; +import { TabThemeColors } from "./components/RecentActivity/EnvironmentPanel/EnvironmentTab/types"; +import { ToggleThemeColors } from "./components/RecentActivity/Toggle/types"; +import { RecentActivityThemeColors } from "./components/RecentActivity/types"; +import { ButtonThemeColors } from "./components/common/NewButton/types"; +import { TooltipThemeColors } from "./components/common/Tooltip/types"; import { Mode } from "./globals"; -export interface ButtonThemeColors { - background?: { - default: string; - hover: string; - focus: string; - disabled: string; - }; - border?: { - default: string; - hover: string; - focus: string; - disabled: string; - }; - icon: { - default: string; - hover: string; - focus: string; - disabled: string; - }; - text: { - default: string; - hover: string; - focus: string; - disabled: string; - }; -} - export interface ThemeColors { + icon: string; button: { primary: ButtonThemeColors; secondary: ButtonThemeColors; tertiary: ButtonThemeColors; }; + tab: TabThemeColors; + tabPanel: { + divider: string; + background: string; + }; + tag: TagThemeColors; + toggle: ToggleThemeColors; + recentActivity: RecentActivityThemeColors; + tooltip: TooltipThemeColors; } declare module "styled-components" { diff --git a/src/typeGuards/isUndefined.ts b/src/typeGuards/isUndefined.ts new file mode 100644 index 000000000..32061236f --- /dev/null +++ b/src/typeGuards/isUndefined.ts @@ -0,0 +1 @@ +export const isUndefined = (x: unknown): x is undefined => x === undefined; From 1649b5cebd26b183e37b7d88c644210464b32a18 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 11 Dec 2023 17:08:41 +0100 Subject: [PATCH 2/5] Fix types --- src/styled.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styled.d.ts b/src/styled.d.ts index f9f34a521..14433d236 100644 --- a/src/styled.d.ts +++ b/src/styled.d.ts @@ -1,9 +1,9 @@ import "styled-components"; -import { TagThemeColors } from "./components/Insights/common/Tag/types"; import { TabThemeColors } from "./components/RecentActivity/EnvironmentPanel/EnvironmentTab/types"; import { ToggleThemeColors } from "./components/RecentActivity/Toggle/types"; import { RecentActivityThemeColors } from "./components/RecentActivity/types"; import { ButtonThemeColors } from "./components/common/NewButton/types"; +import { TagThemeColors } from "./components/common/Tag/types"; import { TooltipThemeColors } from "./components/common/Tooltip/types"; import { Mode } from "./globals"; From 1317db4173fc03008334409c2a189af05ddffdf7 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 11 Dec 2023 17:20:02 +0100 Subject: [PATCH 3/5] Add selected environment type to Register message --- src/components/RecentActivity/EnvironmentTypePanel/index.tsx | 2 +- src/components/RecentActivity/index.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx index 860ade15e..c5bc08417 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx @@ -44,7 +44,7 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { icon: , button: ( handleEnvironmentTypeButtonClick("local")} + onClick={() => handleEnvironmentTypeButtonClick("shared")} label={"Learn more"} buttonType={"secondary"} size={"large"} diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index f8e960133..62bc4b23a 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -330,7 +330,8 @@ export const RecentActivity = (props: RecentActivityProps) => { window.sendMessageToDigma({ action: actions.REGISTER, payload: { - ...formData + ...formData, + selectedEnvironmentType: environmentToSetType?.type } }); From 587c5a668562c5d6a95c790898ece9342f7d203e Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 11 Dec 2023 17:45:59 +0100 Subject: [PATCH 4/5] Fix tooltips --- src/components/common/App/styles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/common/App/styles.ts b/src/components/common/App/styles.ts index eda5385b5..8d25d13b2 100644 --- a/src/components/common/App/styles.ts +++ b/src/components/common/App/styles.ts @@ -4,8 +4,8 @@ import { platform } from "../../../platform"; export const LAYERS = { MODAL: 1000, - TOOLTIP: 2000, - OVERLAY: 3000 + OVERLAY: 2000, + TOOLTIP: 3000 }; export const getMainFont = (customFont: string) => { From 0f140a7baf977a720bc75aeb53c5dc99df31d9f0 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Mon, 11 Dec 2023 17:51:01 +0100 Subject: [PATCH 5/5] Update time duration format --- src/components/RecentActivity/RecentActivityTable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/RecentActivity/RecentActivityTable/index.tsx b/src/components/RecentActivity/RecentActivityTable/index.tsx index e120d7ddf..c39d242db 100644 --- a/src/components/RecentActivity/RecentActivityTable/index.tsx +++ b/src/components/RecentActivity/RecentActivityTable/index.tsx @@ -44,7 +44,7 @@ const renderBadge = () => ( const renderTimeDistance = (timestamp: string, viewMode: ViewMode) => { const title = new Date(timestamp).toString(); const timeDistanceString = formatTimeDistance(timestamp, { - format: "short", + format: "medium", withDescriptiveWords: false });