From c841763421b9b0dbb7ea1297e338c6640b53422a Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 4 Dec 2024 07:09:29 +0100 Subject: [PATCH 1/5] Increase maximum Treemap width --- .../Dashboard/MetricsReport/Chart/index.tsx | 11 ++- .../common/TreeMap/TreeMap.stories.tsx | 4 + src/components/common/TreeMap/index.tsx | 85 ++++++++++++++++--- src/components/common/TreeMap/types.ts | 4 + 4 files changed, 93 insertions(+), 11 deletions(-) diff --git a/src/components/Dashboard/MetricsReport/Chart/index.tsx b/src/components/Dashboard/MetricsReport/Chart/index.tsx index a744eec28..df6053342 100644 --- a/src/components/Dashboard/MetricsReport/Chart/index.tsx +++ b/src/components/Dashboard/MetricsReport/Chart/index.tsx @@ -58,7 +58,16 @@ export const Chart = ({ return ( - + ); }; diff --git a/src/components/common/TreeMap/TreeMap.stories.tsx b/src/components/common/TreeMap/TreeMap.stories.tsx index 7d4f625c8..43230a22a 100644 --- a/src/components/common/TreeMap/TreeMap.stories.tsx +++ b/src/components/common/TreeMap/TreeMap.stories.tsx @@ -21,6 +21,10 @@ export const Default: Story = { width: 800, height: 800, padding: 40, + minTileDimensions: { + width: 100, + height: 100 + }, data: [ { id: "payment", diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index fe4bbad35..fa5820a3d 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -1,29 +1,90 @@ -import squarify from "squarify"; +import squarify, { Input } from "squarify"; +import { logger } from "../../../logging"; import { isNull } from "../../../typeGuards/isNull"; -import { TreeMapProps } from "./types"; +import { TileData, TreeMapProps } from "./types"; -export const TreeMap = ({ padding = 0, data, width, height }: TreeMapProps) => { +const calculateTiles = ( + data: Input[], + container: { x0: number; y0: number; x1: number; y1: number }, + minTileDimensions?: { + width: number; + height: number; + } +) => { + logger.info("render"); + let containerDimensions = container; + let tiles = squarify(data, containerDimensions); + + let areTilesValid = + !minTileDimensions || + (minTileDimensions && + tiles.every( + (tile) => + tile.x1 - tile.x0 >= minTileDimensions.width && + tile.y1 - tile.y0 >= minTileDimensions.height + )); + + // const MAX_WIDTH = 10000; + const MAX_ITERATIONS = 100; + let iterations = 0; + let currentWidth = containerDimensions.x1; + + while (!areTilesValid && iterations < MAX_ITERATIONS) { + containerDimensions = { + ...containerDimensions, + x1: Math.trunc(currentWidth * 1.1) + }; + + tiles = squarify(data, containerDimensions); + + logger.info("currentWidth", currentWidth); + logger.info("iterations", iterations); + + areTilesValid = + !minTileDimensions || + (minTileDimensions && + tiles.every( + (tile) => + tile.x1 - tile.x0 >= minTileDimensions.width && + tile.y1 - tile.y0 >= minTileDimensions.height + )); + + iterations++; + currentWidth = containerDimensions.x1; + } + + return { tiles, container: containerDimensions }; +}; + +export const TreeMap = ({ + padding = 0, + data, + width, + height, + minTileDimensions +}: TreeMapProps) => { const container = { x0: 0, y0: 0, x1: width, y1: height }; const dataMax = Math.max(...data.map((item) => item.value)); - const minNormalizedValue = dataMax > 0 ? dataMax * 0.05 : 1; + const minNormalizedValue = dataMax > 0 ? dataMax * 0.01 : 1; const normalizedData = data.map((item, index) => { return { - id: index, + id: String(index), value: item.value < minNormalizedValue ? minNormalizedValue : item.value, content: item.content }; }); const sortedData = [...normalizedData].sort((a, b) => b.value - a.value); - const tiles = squarify(sortedData, container); + + const tilesData = calculateTiles(sortedData, container, minTileDimensions); // Transform coordinates to add paddings between tiles const transformedTiles = padding - ? tiles.map((tile) => { + ? tilesData.tiles.map((tile) => { const isLeftEdge = tile.x0 === 0; const isTopEdge = tile.y0 === 0; - const isRightEdge = tile.x1 - width < 1; - const isBottomEdge = tile.y1 - height < 1; + const isRightEdge = tile.x1 - tilesData.container.x1 < 1; + const isBottomEdge = tile.y1 - tilesData.container.y1 < 1; return { ...tile, @@ -33,12 +94,16 @@ export const TreeMap = ({ padding = 0, data, width, height }: TreeMapProps) => { y1: isBottomEdge ? tile.y1 : tile.y1 - padding }; }) - : tiles; + : tilesData.tiles; + + logger.info("tilesData", tilesData); return (
[]; width: number; height: number; + minTileDimensions?: { + width: number; + height: number; + }; } From c4ab086305f2efcacd93a0b2f77356bee800429a Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Wed, 4 Dec 2024 14:47:43 +0100 Subject: [PATCH 2/5] Add gradient overlays --- package.json | 10 ++-- .../Dashboard/MetricsReport/Chart/index.tsx | 59 +++++++++++++++---- .../Dashboard/MetricsReport/Chart/styles.ts | 35 ++++++++++- .../EnvironmentTypeCard/types.ts | 3 +- .../EnvironmentTypes/InsightCard/types.ts | 3 +- .../pages/EnvironmentTypes/types.ts | 3 +- .../NewErrorCard/OccurrenceChart/index.tsx | 8 +-- .../Highlights/EmptyStateCard/types.ts | 4 +- .../TopIssues/common/HighlightCard/types.ts | 3 +- .../CodeButton/GlowingIconButton/types.ts | 4 +- .../Navigation/common/IconButton/types.ts | 4 +- .../Navigation/common/MenuList/types.ts | 4 +- src/components/RecentActivity/Badge/index.tsx | 4 +- .../RecentActivity/LiveView/index.tsx | 8 +-- src/components/common/Badge/index.tsx | 4 +- src/components/common/Badge/types.ts | 8 ++- src/components/common/Button/index.tsx | 4 +- src/components/common/EmptyState/types.ts | 4 +- src/components/common/IconButton/index.tsx | 3 +- src/components/common/IconButton/types.ts | 5 +- .../JiraTicket/TicketLinkButton/index.tsx | 4 +- .../JiraTicket/TicketLinkButton/types.ts | 4 +- src/components/common/JiraTicket/types.ts | 4 +- src/components/common/Loader/index.tsx | 4 +- src/components/common/NewButton/types.ts | 4 +- src/components/common/Popover/index.tsx | 3 +- src/components/common/Popover/types.ts | 7 +-- src/components/common/RegisterForm/index.tsx | 4 +- .../RegistrationDialog/TextField/types.ts | 4 +- src/components/common/Tooltip/types.ts | 4 +- src/components/common/TreeMap/index.tsx | 42 ++++--------- src/components/common/v3/Button/types.ts | 4 +- src/components/common/v3/IconButton/index.tsx | 4 +- src/components/common/v3/NewButton/types.ts | 4 +- .../common/v3/NewEmptyState/types.ts | 4 +- .../common/v3/NewIconButton/index.tsx | 4 +- src/components/common/v3/TextField/types.ts | 9 ++- src/components/common/v3/Tooltip/types.ts | 4 +- src/hooks/usePagination.ts | 4 +- 39 files changed, 182 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index 30e636452..f7ef27698 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,12 @@ "storybook": "storybook dev -p 6006", "start": "npm run storybook", "build-storybook": "storybook build", - "build:dev": "webpack --config webpack.dev.ts", - "build:dev:jetbrains": "webpack --config webpack.dev.ts --env platform=JetBrains", + "build:dev": "webpack --progress --config webpack.dev.ts", + "build:dev:jetbrains": "webpack --progress --config webpack.dev.ts --env platform=JetBrains", "build:dev:web": "webpack --config webpack.dev.ts --env platform=Web", - "build:prod": "webpack --config webpack.prod.ts", - "build:prod:jetbrains": "webpack --config webpack.prod.ts --env platform=JetBrains", - "build:prod:web": "webpack --config webpack.prod.ts --env platform=Web", + "build:prod": "webpack --progress --config webpack.prod.ts", + "build:prod:jetbrains": "webpack --progress --config webpack.prod.ts --env platform=JetBrains", + "build:prod:web": "webpack --progress --config webpack.prod.ts --env platform=Web", "precommit": "lint-staged", "prepare": "husky" }, diff --git a/src/components/Dashboard/MetricsReport/Chart/index.tsx b/src/components/Dashboard/MetricsReport/Chart/index.tsx index df6053342..5a0c952ec 100644 --- a/src/components/Dashboard/MetricsReport/Chart/index.tsx +++ b/src/components/Dashboard/MetricsReport/Chart/index.tsx @@ -1,4 +1,6 @@ +import { UIEvent, useEffect, useState } from "react"; import useDimensions from "react-cool-dimensions"; +import useScrollbarSize from "react-scrollbar-size"; import { Input } from "squarify"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; import { TreeMap } from "../../../common/TreeMap"; @@ -16,7 +18,21 @@ export const Chart = ({ timeMode, viewLevel }: ChartProps) => { - const { width, height, observe } = useDimensions(); + const { width, height, entry, observe } = useDimensions(); + const [isLeftOverlayVisible, setIsLeftOverlayVisible] = useState(false); + const [isRightOverlayVisible, setIsRightOverlayVisible] = useState(false); + const scrollbar = useScrollbarSize(); + + useEffect(() => { + if (entry) { + setIsLeftOverlayVisible(entry.target.scrollLeft > 0); + + setIsRightOverlayVisible( + entry.target.scrollWidth > width && + entry.target.scrollLeft + width !== entry.target.scrollWidth + ); + } + }, [width, entry]); const chartData: Input[] = data.map((x) => { const score = x.score; @@ -56,17 +72,38 @@ export const Chart = ({ }; }); + const handleContainerScroll = (e: UIEvent) => { + setIsLeftOverlayVisible(e.currentTarget.scrollLeft > 0); + setIsRightOverlayVisible( + e.currentTarget.scrollWidth > width && + e.currentTarget.scrollLeft + e.currentTarget.clientWidth !== + e.currentTarget.scrollWidth + ); + }; + return ( - - + + + + + ); diff --git a/src/components/Dashboard/MetricsReport/Chart/styles.ts b/src/components/Dashboard/MetricsReport/Chart/styles.ts index cb5735fd4..0fcdf38e9 100644 --- a/src/components/Dashboard/MetricsReport/Chart/styles.ts +++ b/src/components/Dashboard/MetricsReport/Chart/styles.ts @@ -1,6 +1,39 @@ -import styled from "styled-components"; +import styled, { css } from "styled-components"; export const Container = styled.div` width: 100%; height: 100%; + position: relative; +`; + +export const ContentContainer = styled.div` + width: 100%; + height: 100%; + overflow-x: auto; +`; + +export const Overlay = styled.div<{ + $placement: "left" | "right"; + $visible: boolean; +}>` + position: absolute; + top: 0; + bottom: 0; + ${({ $placement }) => + $placement === "left" + ? css` + left: 0; + ` + : css` + right: 0; + `} + width: 70px; + background: linear-gradient( + ${({ $placement }) => ($placement === "left" ? "90" : "270")}deg, + #1a1b1e 0%, + rgb(26 27 30 / 0%) 100% + ); + opacity: ${({ $visible }) => ($visible ? 1 : 0)}; + transition: opacity 300ms; + pointer-events: none; `; diff --git a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts index 88cfa2e69..5001d62e5 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts @@ -1,3 +1,4 @@ +import { MemoExoticComponent } from "react"; import { ThemeableIconProps } from "../../../../common/icons/types"; import { InsightCardType } from "../InsightCard/types"; @@ -5,7 +6,7 @@ export type EnvironmentStatus = "active" | "waiting-for-data"; export interface EnvironmentTypeCardProps { name: string; - icon: React.MemoExoticComponent< + icon: MemoExoticComponent< (props: ThemeableIconProps & { height: number }) => JSX.Element >; description?: string; diff --git a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts index a5ad9da6a..c1d66fcda 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts @@ -1,3 +1,4 @@ +import { MemoExoticComponent } from "react"; import { IconProps } from "../../../../common/icons/types"; export interface InsightCardProps { @@ -15,7 +16,7 @@ export interface CountChipProps { } export interface InsightCardTypeData { - icon?: React.MemoExoticComponent<(props: IconProps) => JSX.Element>; + icon?: MemoExoticComponent<(props: IconProps) => JSX.Element>; name: string; description: string; } diff --git a/src/components/Documentation/pages/EnvironmentTypes/types.ts b/src/components/Documentation/pages/EnvironmentTypes/types.ts index 6361b72a5..dee6e3db2 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/types.ts @@ -1,10 +1,11 @@ +import { MemoExoticComponent } from "react"; import { ThemeableIconProps } from "../../../common/icons/types"; import { EnvironmentStatus } from "./EnvironmentTypeCard/types"; import { InsightCardType } from "./InsightCard/types"; export interface EnvironmentTypeData { id: string; - icon: React.MemoExoticComponent< + icon: MemoExoticComponent< (props: ThemeableIconProps & { height: number }) => JSX.Element >; name: string; diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx index fea7db066..ff6cb3a5b 100644 --- a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx +++ b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx @@ -1,5 +1,5 @@ import { format } from "date-fns"; -import { useEffect, useMemo, useState } from "react"; +import { SVGProps, useEffect, useMemo, useState } from "react"; import { Bar, BarChart, @@ -45,18 +45,18 @@ export const OccurrenceChart = ({ const { environment } = useConfigSelector(); const environmentId = environment?.id; - const tickLabelStyles: React.SVGProps = { + const tickLabelStyles: SVGProps = { fill: theme.colors.v3.text.secondary, opacity: 0.5 }; - const YAxisTickLabelStyles: React.SVGProps = { + const YAxisTickLabelStyles: SVGProps = { ...tickLabelStyles, fontSize: theme.typographies.footNote.fontSize, fontWeight: theme.typographies.footNote.fontWeight.regular }; - const XAxisTickLabelStyles: React.SVGProps = { + const XAxisTickLabelStyles: SVGProps = { ...tickLabelStyles, fontSize: theme.typographies.captionOne.fontSize, fontWeight: theme.typographies.captionOne.fontWeight.regular diff --git a/src/components/Highlights/EmptyStateCard/types.ts b/src/components/Highlights/EmptyStateCard/types.ts index 422c7b80c..ec0b51cb7 100644 --- a/src/components/Highlights/EmptyStateCard/types.ts +++ b/src/components/Highlights/EmptyStateCard/types.ts @@ -1,4 +1,4 @@ -import { ComponentType } from "react"; +import { ComponentType, ReactNode } from "react"; import { IconProps } from "../../common/icons/types"; type EmptyStateType = "default" | "success" | "lowSeverity"; @@ -8,7 +8,7 @@ export interface EmptyStateCardProps { icon: ComponentType; title?: string; text?: string; - customContent?: React.ReactNode; + customContent?: ReactNode; blurredContent?: JSX.Element; } diff --git a/src/components/Highlights/TopIssues/common/HighlightCard/types.ts b/src/components/Highlights/TopIssues/common/HighlightCard/types.ts index 0492cdcfa..c98bc9a27 100644 --- a/src/components/Highlights/TopIssues/common/HighlightCard/types.ts +++ b/src/components/Highlights/TopIssues/common/HighlightCard/types.ts @@ -1,6 +1,7 @@ +import { ReactNode } from "react"; import { GenericMetrics, HighlightData } from "../../types"; export interface HighlightCardProps { - content: React.ReactNode; + content: ReactNode; highlight: HighlightData; } diff --git a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts index ce3ad7321..3bb36636f 100644 --- a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts +++ b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts @@ -1,7 +1,9 @@ +import { ReactNode } from "react"; + type GlowingIconButtonType = "default" | "error"; export interface GlowingIconButtonProps { - icon: React.ReactNode; + icon: ReactNode; onClick?: () => void; type?: GlowingIconButtonType; } diff --git a/src/components/Navigation/common/IconButton/types.ts b/src/components/Navigation/common/IconButton/types.ts index 0954c599e..8b19a5c69 100644 --- a/src/components/Navigation/common/IconButton/types.ts +++ b/src/components/Navigation/common/IconButton/types.ts @@ -1,7 +1,7 @@ -import React from "react"; +import { ReactNode } from "react"; export interface IconButtonProps { - icon: React.ReactNode; + icon: ReactNode; isDisabled?: boolean; className?: string; onClick?: () => void; diff --git a/src/components/Navigation/common/MenuList/types.ts b/src/components/Navigation/common/MenuList/types.ts index 69d4cfb19..787e5f3a7 100644 --- a/src/components/Navigation/common/MenuList/types.ts +++ b/src/components/Navigation/common/MenuList/types.ts @@ -21,10 +21,10 @@ export interface ListItemIconContainerProps { } export interface MenuItem { - customContent?: React.ReactNode; + customContent?: ReactNode; id: string; label?: string; - icon?: React.ReactNode; + icon?: ReactNode; onClick?: () => void; isDisabled?: boolean; groupName?: string; diff --git a/src/components/RecentActivity/Badge/index.tsx b/src/components/RecentActivity/Badge/index.tsx index 14f354297..3666f3ea0 100644 --- a/src/components/RecentActivity/Badge/index.tsx +++ b/src/components/RecentActivity/Badge/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { memo } from "react"; import * as s from "./styles"; import { BadgeProps } from "./types"; @@ -6,4 +6,4 @@ const BadgeComponent = ({ size = "small", className }: BadgeProps) => ( ); -export const Badge = React.memo(BadgeComponent); +export const Badge = memo(BadgeComponent); diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 57f0ce7e5..eaa81237c 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,5 +1,6 @@ import { format } from "date-fns"; import { + MouseEvent, UIEvent, useCallback, useEffect, @@ -253,10 +254,7 @@ export const LiveView = ({ data, onClose }: LiveViewProps) => { ); }; - const handleAreaMouseMove = ( - props: unknown, - e: React.MouseEvent - ) => { + const handleAreaMouseMove = (props: unknown, e: MouseEvent) => { setAreaTooltip({ x: e.clientX, y: e.clientY }); }; @@ -266,7 +264,7 @@ export const LiveView = ({ data, onClose }: LiveViewProps) => { const handleDotMouseOver = ( props: unknown, - e: React.MouseEvent + e: MouseEvent ) => { setDotTooltip({ coordinates: { x: e.clientX, y: e.clientY }, diff --git a/src/components/common/Badge/index.tsx b/src/components/common/Badge/index.tsx index 6ac495ba7..f4ff067ab 100644 --- a/src/components/common/Badge/index.tsx +++ b/src/components/common/Badge/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { memo } from "react"; import * as s from "./styles"; import { BadgeProps } from "./types"; @@ -8,4 +8,4 @@ const BadgeComponent = ({ customStyles }: BadgeProps) => ( ); -export const Badge = React.memo(BadgeComponent); +export const Badge = memo(BadgeComponent); diff --git a/src/components/common/Badge/types.ts b/src/components/common/Badge/types.ts index 4a5ddf317..2841d9cbb 100644 --- a/src/components/common/Badge/types.ts +++ b/src/components/common/Badge/types.ts @@ -1,10 +1,12 @@ +import { CSSProperties } from "react"; + export interface BadgeProps { customStyles?: { - main: React.CSSProperties; - outline: React.CSSProperties; + main: CSSProperties; + outline: CSSProperties; }; } export interface CustomStylesProps { - $customStyles?: React.CSSProperties; + $customStyles?: CSSProperties; } diff --git a/src/components/common/Button/index.tsx b/src/components/common/Button/index.tsx index 97bdd755b..00c7ab41c 100644 --- a/src/components/common/Button/index.tsx +++ b/src/components/common/Button/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { ButtonProps } from "./types"; @@ -14,7 +14,7 @@ export const ButtonComponent = ( }: ButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/EmptyState/types.ts b/src/components/common/EmptyState/types.ts index 7b8b0a102..32395d8c7 100644 --- a/src/components/common/EmptyState/types.ts +++ b/src/components/common/EmptyState/types.ts @@ -1,8 +1,8 @@ -import { ReactNode } from "react"; +import { ComponentType, ReactNode } from "react"; import { ThemeableIconProps } from "../../common/icons/types"; export interface EmptyStateProps { - icon?: React.ComponentType; + icon?: ComponentType; title?: string; content?: ReactNode; } diff --git a/src/components/common/IconButton/index.tsx b/src/components/common/IconButton/index.tsx index db64c724b..5e6e0cfc9 100644 --- a/src/components/common/IconButton/index.tsx +++ b/src/components/common/IconButton/index.tsx @@ -1,3 +1,4 @@ +import { MouseEvent } from "react"; import * as s from "./styles"; import { IconButtonProps } from "./types"; @@ -6,7 +7,7 @@ export const IconButton = ({ disabled, icon: Icon }: IconButtonProps) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { onClick(e); }; diff --git a/src/components/common/IconButton/types.ts b/src/components/common/IconButton/types.ts index fc136d1b0..76b7c790d 100644 --- a/src/components/common/IconButton/types.ts +++ b/src/components/common/IconButton/types.ts @@ -1,7 +1,8 @@ +import { ComponentType, MouseEventHandler } from "react"; import { IconProps } from "../icons/types"; export interface IconButtonProps { - icon: React.ComponentType; - onClick: React.MouseEventHandler; + icon: ComponentType; + onClick: MouseEventHandler; disabled?: boolean; } diff --git a/src/components/common/JiraTicket/TicketLinkButton/index.tsx b/src/components/common/JiraTicket/TicketLinkButton/index.tsx index 3ef1082d9..9f17d1e9d 100644 --- a/src/components/common/JiraTicket/TicketLinkButton/index.tsx +++ b/src/components/common/JiraTicket/TicketLinkButton/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { ChangeEvent, useEffect, useState } from "react"; import { isValidHttpUrl } from "../../../../utils/isValidUrl"; import { Button } from "../../Button"; import { ActionableTextField } from "../ActionableTextField"; @@ -13,7 +13,7 @@ export const TicketLinkButton = ({ ticketLink?.errorMessage ?? null ); const [link, setLink] = useState(ticketLink?.link ?? null); - const onTicketLinkChange = (event: React.ChangeEvent) => { + const onTicketLinkChange = (event: ChangeEvent) => { const ticketLink = event.target.value; setLink(ticketLink); if (!ticketLink || isValidHttpUrl(ticketLink)) { diff --git a/src/components/common/JiraTicket/TicketLinkButton/types.ts b/src/components/common/JiraTicket/TicketLinkButton/types.ts index 8554f29ef..83df5c088 100644 --- a/src/components/common/JiraTicket/TicketLinkButton/types.ts +++ b/src/components/common/JiraTicket/TicketLinkButton/types.ts @@ -1,9 +1,11 @@ +import { ChangeEvent } from "react"; + export interface TicketLinkButtonProps { ticketLink?: { link?: string | null; errorMessage?: string | null; }; - onTicketLinkChange?: (event: React.ChangeEvent) => void; + onTicketLinkChange?: (event: ChangeEvent) => void; unlinkTicket?: () => void; linkTicket?: (value: string) => void; } diff --git a/src/components/common/JiraTicket/types.ts b/src/components/common/JiraTicket/types.ts index 5b02af8a1..98d21189c 100644 --- a/src/components/common/JiraTicket/types.ts +++ b/src/components/common/JiraTicket/types.ts @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import { ChangeEvent, ReactNode } from "react"; export interface JiraTicketThemeColors { background: string; @@ -27,7 +27,7 @@ export interface JiraTicketProps { link?: string | null; errorMessage?: string | null; }; - onTicketLinkChange?: (event: React.ChangeEvent) => void; + onTicketLinkChange?: (event: ChangeEvent) => void; unlinkTicket?: () => void; linkTicket?: (link: string) => void; } diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx index c6e6c5316..78f5f4ce1 100644 --- a/src/components/common/Loader/index.tsx +++ b/src/components/common/Loader/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-curly-brace-presence */ -import React from "react"; +import { memo } from "react"; import { CircleLoader } from "../CircleLoader"; import { LoaderProps } from "./types"; @@ -365,4 +365,4 @@ const LoaderComponent = ({ size = 20, status, themeKind }: LoaderProps) => { } }; -export const Loader = React.memo(LoaderComponent); +export const Loader = memo(LoaderComponent); diff --git a/src/components/common/NewButton/types.ts b/src/components/common/NewButton/types.ts index 9e8fce05a..b6a9baa78 100644 --- a/src/components/common/NewButton/types.ts +++ b/src/components/common/NewButton/types.ts @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes } from "react"; +import { ButtonHTMLAttributes, ComponentType } from "react"; import { IconProps } from "../../common/icons/types"; export interface ButtonThemeColors { @@ -32,7 +32,7 @@ export type ButtonType = "primary" | "secondary" | "tertiary"; export type ButtonSize = "small" | "large"; export interface NewButtonProps { - icon?: React.ComponentType; + icon?: ComponentType; label?: string; title?: string; onClick?: () => void; diff --git a/src/components/common/Popover/index.tsx b/src/components/common/Popover/index.tsx index 9b3b3127e..2ea1b16d8 100644 --- a/src/components/common/Popover/index.tsx +++ b/src/components/common/Popover/index.tsx @@ -1,5 +1,6 @@ // Source: https://floating-ui.com/docs/popover#reusable-popover-component +import { ReactNode } from "react"; import { PopoverContext, usePopover } from "./hooks"; import { PopoverProps } from "./types"; @@ -9,7 +10,7 @@ export function Popover({ modal = false, ...restOptions }: { - children: React.ReactNode; + children: ReactNode; } & PopoverProps) { // This can accept any props as options, e.g. `placement`, // or other positioning options. diff --git a/src/components/common/Popover/types.ts b/src/components/common/Popover/types.ts index a50d019f0..a4234a46c 100644 --- a/src/components/common/Popover/types.ts +++ b/src/components/common/Popover/types.ts @@ -1,12 +1,11 @@ import { Placement } from "@floating-ui/react"; +import { Dispatch, SetStateAction } from "react"; import { usePopover } from "./hooks"; export type ContextType = | (ReturnType & { - setLabelId: React.Dispatch>; - setDescriptionId: React.Dispatch< - React.SetStateAction - >; + setLabelId: Dispatch>; + setDescriptionId: Dispatch>; }) | null; diff --git a/src/components/common/RegisterForm/index.tsx b/src/components/common/RegisterForm/index.tsx index a8fbb0d77..cc585212a 100644 --- a/src/components/common/RegisterForm/index.tsx +++ b/src/components/common/RegisterForm/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo } from "react"; +import { FormEventHandler, useContext, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { actions as globalActions } from "../../../actions"; @@ -71,7 +71,7 @@ export const RegisterForm = ({ onNext(true); }; - const handleOnSubmit: React.FormEventHandler = (e) => { + const handleOnSubmit: FormEventHandler = (e) => { e.preventDefault(); void handleSubmit(onSubmit)(e); }; diff --git a/src/components/common/RegistrationDialog/TextField/types.ts b/src/components/common/RegistrationDialog/TextField/types.ts index 5b76d7929..0ec9dd99f 100644 --- a/src/components/common/RegistrationDialog/TextField/types.ts +++ b/src/components/common/RegistrationDialog/TextField/types.ts @@ -1,9 +1,9 @@ -import { ChangeEventHandler, FocusEventHandler } from "react"; +import { ChangeEventHandler, ComponentType, FocusEventHandler } from "react"; import { IconProps } from "../../icons/types"; export interface TextFieldProps { placeholder?: string; - icon?: React.ComponentType; + icon?: ComponentType; value: string; onChange: ChangeEventHandler; className?: string; diff --git a/src/components/common/Tooltip/types.ts b/src/components/common/Tooltip/types.ts index f20bb78b9..00e7c9bbb 100644 --- a/src/components/common/Tooltip/types.ts +++ b/src/components/common/Tooltip/types.ts @@ -1,5 +1,5 @@ import { Placement } from "@floating-ui/react"; -import { ReactElement, ReactNode } from "react"; +import { CSSProperties, ReactElement, ReactNode } from "react"; export interface TooltipThemeColors { background: string; @@ -10,6 +10,6 @@ export interface TooltipProps { children: ReactElement; title: ReactNode; placement?: Placement; - style?: React.CSSProperties; + style?: CSSProperties; isOpen?: boolean; } diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index fa5820a3d..9dad84109 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -1,5 +1,4 @@ -import squarify, { Input } from "squarify"; -import { logger } from "../../../logging"; +import squarify, { ILayoutRect, Input } from "squarify"; import { isNull } from "../../../typeGuards/isNull"; import { TileData, TreeMapProps } from "./types"; @@ -11,34 +10,13 @@ const calculateTiles = ( height: number; } ) => { - logger.info("render"); - let containerDimensions = container; - let tiles = squarify(data, containerDimensions); - - let areTilesValid = - !minTileDimensions || - (minTileDimensions && - tiles.every( - (tile) => - tile.x1 - tile.x0 >= minTileDimensions.width && - tile.y1 - tile.y0 >= minTileDimensions.height - )); - - // const MAX_WIDTH = 10000; + let tiles: ILayoutRect[] = []; + let areTilesValid = false; const MAX_ITERATIONS = 100; let iterations = 0; - let currentWidth = containerDimensions.x1; while (!areTilesValid && iterations < MAX_ITERATIONS) { - containerDimensions = { - ...containerDimensions, - x1: Math.trunc(currentWidth * 1.1) - }; - - tiles = squarify(data, containerDimensions); - - logger.info("currentWidth", currentWidth); - logger.info("iterations", iterations); + tiles = squarify(data, container); areTilesValid = !minTileDimensions || @@ -49,11 +27,15 @@ const calculateTiles = ( tile.y1 - tile.y0 >= minTileDimensions.height )); + container = { + ...container, + x1: Math.trunc(container.x1 * 1.1) + }; + iterations++; - currentWidth = containerDimensions.x1; } - return { tiles, container: containerDimensions }; + return { tiles, container }; }; export const TreeMap = ({ @@ -96,14 +78,10 @@ export const TreeMap = ({ }) : tilesData.tiles; - logger.info("tilesData", tilesData); - return (
; + icon?: ComponentType; label?: string; buttonType?: ButtonType; } diff --git a/src/components/common/v3/IconButton/index.tsx b/src/components/common/v3/IconButton/index.tsx index 3c95bc04d..9dc898bb6 100644 --- a/src/components/common/v3/IconButton/index.tsx +++ b/src/components/common/v3/IconButton/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { IconButtonProps } from "./types"; @@ -7,7 +7,7 @@ const IconButtonComponent = ( { onClick, className, disabled, icon }: IconButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/v3/NewButton/types.ts b/src/components/common/v3/NewButton/types.ts index 2abde8024..74fab581c 100644 --- a/src/components/common/v3/NewButton/types.ts +++ b/src/components/common/v3/NewButton/types.ts @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes } from "react"; +import { ButtonHTMLAttributes, ComponentType } from "react"; import { IconProps } from "../../icons/types"; export type ButtonType = @@ -8,7 +8,7 @@ export type ButtonType = | "secondaryBorderless"; export interface BaseButtonProps { - icon?: React.ComponentType; + icon?: ComponentType; label?: string; buttonType?: ButtonType; } diff --git a/src/components/common/v3/NewEmptyState/types.ts b/src/components/common/v3/NewEmptyState/types.ts index 8f93726d0..c49dde65b 100644 --- a/src/components/common/v3/NewEmptyState/types.ts +++ b/src/components/common/v3/NewEmptyState/types.ts @@ -1,8 +1,8 @@ -import { ReactNode } from "react"; +import { ComponentType, ReactNode } from "react"; import { IconProps } from "../../../common/icons/types"; export interface EmptyStateProps { - icon?: React.ComponentType; + icon?: ComponentType; title?: string; content?: ReactNode; } diff --git a/src/components/common/v3/NewIconButton/index.tsx b/src/components/common/v3/NewIconButton/index.tsx index 1a07b1721..ffa111383 100644 --- a/src/components/common/v3/NewIconButton/index.tsx +++ b/src/components/common/v3/NewIconButton/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { ButtonType, NewIconButtonProps } from "./types"; @@ -25,7 +25,7 @@ const NewIconButtonComponent = ( }: NewIconButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/v3/TextField/types.ts b/src/components/common/v3/TextField/types.ts index e4241c761..672c1ad53 100644 --- a/src/components/common/v3/TextField/types.ts +++ b/src/components/common/v3/TextField/types.ts @@ -1,4 +1,9 @@ -import { ChangeEventHandler, HTMLInputTypeAttribute, ReactNode } from "react"; +import { + ChangeEventHandler, + ComponentType, + HTMLInputTypeAttribute, + ReactNode +} from "react"; import { IconProps } from "../../icons/types"; export interface TextFieldProps { @@ -11,7 +16,7 @@ export interface TextFieldProps { isInvalid?: boolean; error?: string; type?: HTMLInputTypeAttribute; - icon?: React.ComponentType; + icon?: ComponentType; alwaysRenderError?: boolean; } diff --git a/src/components/common/v3/Tooltip/types.ts b/src/components/common/v3/Tooltip/types.ts index 85df28817..6a84b0cfe 100644 --- a/src/components/common/v3/Tooltip/types.ts +++ b/src/components/common/v3/Tooltip/types.ts @@ -1,5 +1,5 @@ import { Placement } from "@floating-ui/react"; -import { ReactElement, ReactNode } from "react"; +import { CSSProperties, ReactElement, ReactNode } from "react"; export interface TooltipThemeColors { background: string; @@ -10,7 +10,7 @@ export interface TooltipProps { children: ReactElement; title: ReactNode; placement?: Placement; - style?: React.CSSProperties; + style?: CSSProperties; isOpen?: boolean; fullWidth?: boolean; isDisabled?: boolean; diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts index eeb5d9679..e6a05c124 100644 --- a/src/hooks/usePagination.ts +++ b/src/hooks/usePagination.ts @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; export const usePagination = ( items: T[], pageSize: number, key?: string -): [T[], number, React.Dispatch>] => { +): [T[], number, Dispatch>] => { const [page, setPage] = useState(0); const pageCount = Math.ceil(items.length / pageSize) || 1; From 35479f12b4596ca15fb5e4c1d0828faf3fe49701 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 5 Dec 2024 12:52:11 +0100 Subject: [PATCH 3/5] Normalize data --- src/components/common/TreeMap/index.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index 9dad84109..f784acd15 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -2,6 +2,14 @@ import squarify, { ILayoutRect, Input } from "squarify"; import { isNull } from "../../../typeGuards/isNull"; import { TileData, TreeMapProps } from "./types"; +const minMaxNormalize = ( + value: number, + min: number, + max: number, + newMin: number, + newMax: number +) => ((value - min) * (newMax - newMin)) / (max - min) + newMin; + const calculateTiles = ( data: Input[], container: { x0: number; y0: number; x1: number; y1: number }, @@ -47,15 +55,27 @@ export const TreeMap = ({ }: TreeMapProps) => { const container = { x0: 0, y0: 0, x1: width, y1: height }; + const dataMin = Math.min(...data.map((item) => item.value)) || 1; const dataMax = Math.max(...data.map((item) => item.value)); - const minNormalizedValue = dataMax > 0 ? dataMax * 0.01 : 1; + const NORMALIZED_MIN = 1; + const NORMALIZED_MAX = 10; + // eslint-disable-next-line no-console + console.log("data", data); const normalizedData = data.map((item, index) => { return { id: String(index), - value: item.value < minNormalizedValue ? minNormalizedValue : item.value, + value: minMaxNormalize( + item.value, + dataMin, + dataMax, + NORMALIZED_MIN, + NORMALIZED_MAX + ), content: item.content }; }); + // eslint-disable-next-line no-console + console.log("normalizedData", normalizedData); const sortedData = [...normalizedData].sort((a, b) => b.value - a.value); const tilesData = calculateTiles(sortedData, container, minTileDimensions); From dae228ec2cd7a416ac2af644c0eeac50443bf508 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 5 Dec 2024 14:01:19 +0100 Subject: [PATCH 4/5] Improve data normalization --- src/components/common/TreeMap/index.tsx | 33 ++++++++++++++----------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index f784acd15..63ef59266 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -57,23 +57,28 @@ export const TreeMap = ({ const dataMin = Math.min(...data.map((item) => item.value)) || 1; const dataMax = Math.max(...data.map((item) => item.value)); + const dataMinMaxRatio = dataMax / dataMin; + const MIN_MAX_RATIO = 10; + const NORMALIZED_MAX = MIN_MAX_RATIO; const NORMALIZED_MIN = 1; - const NORMALIZED_MAX = 10; // eslint-disable-next-line no-console console.log("data", data); - const normalizedData = data.map((item, index) => { - return { - id: String(index), - value: minMaxNormalize( - item.value, - dataMin, - dataMax, - NORMALIZED_MIN, - NORMALIZED_MAX - ), - content: item.content - }; - }); + const normalizedData = + dataMinMaxRatio > MIN_MAX_RATIO + ? data.map((item) => { + return { + id: item.id, + value: minMaxNormalize( + item.value, + dataMin, + dataMax, + NORMALIZED_MIN, + NORMALIZED_MAX + ), + content: item.content + }; + }) + : data; // eslint-disable-next-line no-console console.log("normalizedData", normalizedData); const sortedData = [...normalizedData].sort((a, b) => b.value - a.value); From baae2d696779e07b05dfdf2c7fc229c8d363c510 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Thu, 5 Dec 2024 14:46:56 +0100 Subject: [PATCH 5/5] Refactor normalization --- src/components/common/TreeMap/index.tsx | 58 ++++++++++--------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index 63ef59266..f99e9ea28 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -2,13 +2,28 @@ import squarify, { ILayoutRect, Input } from "squarify"; import { isNull } from "../../../typeGuards/isNull"; import { TileData, TreeMapProps } from "./types"; -const minMaxNormalize = ( - value: number, - min: number, - max: number, - newMin: number, - newMax: number -) => ((value - min) * (newMax - newMin)) / (max - min) + newMin; +const normalizeData = (data: Input[]) => { + const MIN_MAX_RATIO = 5; + const NORMALIZED_MIN = 1; + const NORMALIZED_MAX = MIN_MAX_RATIO; + + const dataMin = Math.min(...data.map((item) => item.value)) || 1; + const dataMax = Math.max(...data.map((item) => item.value)); + const dataMinMaxRatio = dataMax / dataMin; + + return dataMinMaxRatio > MIN_MAX_RATIO + ? data.map((item) => { + return { + id: item.id, + value: + ((item.value - dataMin) * (NORMALIZED_MAX - NORMALIZED_MIN)) / + (dataMax - dataMin) + + NORMALIZED_MIN, // min-max normalization + content: item.content + }; + }) + : data; +}; const calculateTiles = ( data: Input[], @@ -54,35 +69,8 @@ export const TreeMap = ({ minTileDimensions }: TreeMapProps) => { const container = { x0: 0, y0: 0, x1: width, y1: height }; - - const dataMin = Math.min(...data.map((item) => item.value)) || 1; - const dataMax = Math.max(...data.map((item) => item.value)); - const dataMinMaxRatio = dataMax / dataMin; - const MIN_MAX_RATIO = 10; - const NORMALIZED_MAX = MIN_MAX_RATIO; - const NORMALIZED_MIN = 1; - // eslint-disable-next-line no-console - console.log("data", data); - const normalizedData = - dataMinMaxRatio > MIN_MAX_RATIO - ? data.map((item) => { - return { - id: item.id, - value: minMaxNormalize( - item.value, - dataMin, - dataMax, - NORMALIZED_MIN, - NORMALIZED_MAX - ), - content: item.content - }; - }) - : data; - // eslint-disable-next-line no-console - console.log("normalizedData", normalizedData); + const normalizedData = normalizeData(data); const sortedData = [...normalizedData].sort((a, b) => b.value - a.value); - const tilesData = calculateTiles(sortedData, container, minTileDimensions); // Transform coordinates to add paddings between tiles