Compare commits

..

1 Commits

Author SHA1 Message Date
Gaurav Tewari
3765ca3d42 chore: migrate dropdown 2026-05-20 17:39:33 +05:30
19 changed files with 249 additions and 389 deletions

View File

@@ -45,15 +45,9 @@ jobs:
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
SIGNOZ_BUILDX_GHA_SCOPE: signoz-e2e
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup-buildx
uses: docker/setup-buildx-action@v3
- name: expose-gha-runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: python
uses: actions/setup-python@v5
with:

View File

@@ -69,15 +69,9 @@ jobs:
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-integrate')
runs-on: ubuntu-latest
env:
SIGNOZ_BUILDX_GHA_SCOPE: signoz-integration
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup-buildx
uses: docker/setup-buildx-action@v3
- name: expose-gha-runtime
uses: crazy-max/ghaction-github-runtime@v3
- name: python
uses: actions/setup-python@v5
with:

View File

@@ -34,7 +34,6 @@ DOCKER_BUILD_ARCHS_ENTERPRISE = $(addprefix docker-build-enterprise-,$(ARCHS))
DOCKERFILE_ENTERPRISE = $(SRC)/cmd/enterprise/Dockerfile
DOCKER_REGISTRY_ENTERPRISE ?= docker.io/signoz/signoz
JS_BUILD_CONTEXT = $(SRC)/frontend
DOCKER_BUILDX_PRUNE_FLAGS ?= --force
##############################################################
# directories
@@ -230,21 +229,6 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
@find tests -type f -name "*.pyo" -delete 2>/dev/null || true
@echo ">> python cache cleaned"
.PHONY: py-docker-clean
py-docker-clean: ## Remove Docker image and build caches used by python integration tests
@echo ">> removing SigNoz integration test image"
@docker image rm -f signoz:integration 2>/dev/null || true
@echo ">> removing local integration buildx cache directories"
@rm -rf /tmp/signoz-integration-buildx-cache /tmp/signoz-integration-buildx-cache-next /tmp/signoz-e2e-buildx-cache /tmp/signoz-e2e-buildx-cache-next
@echo ">> pruning docker buildx cache with flags: $(DOCKER_BUILDX_PRUNE_FLAGS)"
@docker buildx prune $(DOCKER_BUILDX_PRUNE_FLAGS)
.PHONY: py-test-clean
py-test-clean: ## Tear down python test stack and remove python/Docker test caches
@$(MAKE) py-test-teardown || true
@$(MAKE) py-clean
@$(MAKE) py-docker-clean
##############################################################
# generate commands

View File

@@ -1,5 +1,3 @@
# syntax=docker/dockerfile:1.13
FROM golang:1.25-bookworm
ARG OS="linux"
@@ -23,8 +21,7 @@ RUN set -eux; \
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
RUN go mod download
COPY ./cmd/ ./cmd/
COPY ./ee/ ./ee/
@@ -32,9 +29,7 @@ COPY ./pkg/ ./pkg/
COPY ./templates/email /root/templates
COPY Makefile Makefile
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
RUN mv /root/linux-${TARGETARCH}/signoz /root/signoz
RUN chmod 755 /root /root/signoz

View File

@@ -1,5 +1,3 @@
# syntax=docker/dockerfile:1.13
FROM node:22-bookworm AS build
WORKDIR /opt/
@@ -32,8 +30,7 @@ RUN set -eux; \
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
RUN go mod download
COPY ./cmd/ ./cmd/
COPY ./ee/ ./ee/
@@ -41,9 +38,7 @@ COPY ./pkg/ ./pkg/
COPY ./templates/email /root/templates
COPY Makefile Makefile
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
RUN TARGET_DIR=/root ARCHS=${TARGETARCH} ZEUS_URL=${ZEUSURL} LICENSE_URL=${ZEUSURL}/api/v1 make go-build-enterprise-race
RUN mv /root/linux-${TARGETARCH}/signoz /root/signoz
COPY --from=build /opt/build ./web/

View File

@@ -1,46 +1,30 @@
import { useState } from 'react';
import { Ellipsis } from '@signozhq/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import { Button } from 'antd';
import './DropDown.styles.scss';
type DropDownItemClick = (info: { key: string; keyPath: string[] }) => void;
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
onDropDownItemClick?: DropDownItemClick;
}): JSX.Element {
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
key: index,
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
const items: MenuItem[] = element.map((e, index) => ({
key: String(index),
label: e,
onClick: onDropDownItemClick,
}));
return (
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
<Button
type="link"
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
>
<DropdownMenuSimple menu={{ items }}>
<Button type="link" className="dropdown-button">
<Ellipsis className="dropdown-icon" size={16} />
</Button>
</Dropdown>
</DropdownMenuSimple>
);
}

View File

@@ -1,15 +1,7 @@
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import {
Button,
Col,
Dropdown,
MenuProps,
Popover,
Row,
Select,
Space,
} from 'antd';
import { Button, Col, Popover, Row, Select, Space } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { Typography } from '@signozhq/ui/typography';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
@@ -241,9 +233,9 @@ function ExplorerCard({
</Popover>
<Share2 onClick={onCopyUrlHandler} size="md" />
{viewKey && (
<Dropdown trigger={['click']} menu={moreOptionMenu}>
<DropdownMenuSimple menu={moreOptionMenu}>
<Ellipsis size="md" />
</Dropdown>
</DropdownMenuSimple>
)}
</Space>
</OffSetCol>

View File

@@ -4,13 +4,13 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { Button, Flex, Switch } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
import useUrlQuery from 'hooks/useUrlQuery';
import { SlidersHorizontal } from '@signozhq/icons';
import { popupContainer } from 'utils/selectPopupContainer';
import ResizeTable from './ResizeTable';
import { DynamicColumnTableProps } from './types';
@@ -85,8 +85,9 @@ function DynamicColumnTable({
);
};
const items: MenuProps['items'] =
const items: MenuItem[] =
dynamicColumns?.map((column, index) => ({
key: String(index),
label: (
<div className="dynamicColumnsTable-items">
<div>{column.title?.toString()}</div>
@@ -96,8 +97,6 @@ function DynamicColumnTable({
/>
</div>
),
key: index,
type: 'checkbox',
})) || [];
// Get current page from URL or default to 1
@@ -126,18 +125,14 @@ function DynamicColumnTable({
<Flex justify="flex-end" align="center" gap={8}>
{facingIssueBtn && <LaunchChatSupport {...facingIssueBtn} />}
{dynamicColumns && (
<Dropdown
getPopupContainer={popupContainer}
menu={{ items }}
trigger={['click']}
>
<DropdownMenuSimple menu={{ items }}>
<Button
className="dynamicColumnTable-button filter-btn"
size="middle"
icon={<SlidersHorizontal size={14} />}
data-testid="additional-filters-button"
/>
</Dropdown>
</DropdownMenuSimple>
)}
</Flex>

View File

@@ -1,6 +1,7 @@
import { useState } from 'react';
import { CloudDownload } from '@signozhq/icons';
import { Button, Dropdown, MenuProps, Flex } from 'antd';
import { DropdownMenuSimple, type MenuProps } from '@signozhq/ui/dropdown-menu';
import { Button, Flex } from 'antd';
import { unparse } from 'papaparse';
import { DownloadProps } from './Download.types';
@@ -67,7 +68,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
};
return (
<Dropdown menu={menu} trigger={['click']}>
<DropdownMenuSimple menu={menu}>
<Button
className="download-button"
loading={isLoading || isDownloading}
@@ -79,7 +80,7 @@ function Download({ data, isLoading, fileName }: DownloadProps): JSX.Element {
Download
</Flex>
</Button>
</Dropdown>
</DropdownMenuSimple>
);
}

View File

@@ -15,7 +15,8 @@ import {
X,
} from '@signozhq/icons';
import { Color } from '@signozhq/design-tokens';
import { Button, Dropdown, Input, MenuProps, Tooltip } from 'antd';
import { Button, Input, Tooltip } from 'antd';
import { DropdownMenuSimple } from '@signozhq/ui/dropdown-menu';
import { Typography } from '@signozhq/ui/typography';
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import ErrorPopover from 'components/ErrorPopover/ErrorPopover';
@@ -128,7 +129,7 @@ function WidgetHeader({
],
);
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
const onMenuItemSelectHandler = useCallback(
({ key }: { key: string }): void => {
if (isTWidgetOptions(key)) {
const functionToCall = keyMethodMapping[key];
@@ -221,8 +222,10 @@ function WidgetHeader({
const menu = useMemo(
() => ({
items: updatedMenuList,
onClick: onMenuItemSelectHandler,
items: updatedMenuList.map((item) => ({
...item,
onClick: onMenuItemSelectHandler,
})),
}),
[updatedMenuList, onMenuItemSelectHandler],
);
@@ -321,7 +324,7 @@ function WidgetHeader({
/>
)}
{menu && Array.isArray(menu.items) && menu.items.length > 0 && (
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
<DropdownMenuSimple menu={menu} side="bottom" align="end">
<Button
data-testid="widget-header-options"
className={`widget-header-more-options ${
@@ -329,7 +332,7 @@ function WidgetHeader({
}`}
icon={<EllipsisVertical size="md" />}
/>
</Dropdown>
</DropdownMenuSimple>
)}
</div>
</>

View File

@@ -1,9 +1,9 @@
import type { MenuItemType } from 'antd/es/menu/hooks/useItems';
import type { MenuItem as DropdownMenuItem } from '@signozhq/ui/dropdown-menu';
import { MenuItemKeys } from './contants';
import { MenuItem } from './types';
export const generateMenuList = (actions: MenuItem[]): MenuItemType[] =>
export const generateMenuList = (actions: MenuItem[]): DropdownMenuItem[] =>
actions
.filter((action: MenuItem) => action.isVisible)
.map(({ key, icon: Icon, label, disabled, ...rest }) => ({

View File

@@ -12,12 +12,11 @@ import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import {
Button,
Dropdown,
Flex,
Input,
MenuProps,
Modal,
Popover,
Skeleton,
@@ -553,7 +552,7 @@ function DashboardsList(): JSX.Element {
];
const getCreateDashboardItems = useMemo(() => {
const menuItems: MenuProps['items'] = [
const menuItems: MenuItem[] = [
{
label: (
<div
@@ -711,11 +710,11 @@ function DashboardsList(): JSX.Element {
{createNewDashboard && (
<section className="actions">
<Dropdown
overlayClassName="new-dashboard-menu"
<DropdownMenuSimple
className="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
placement="bottomRight"
trigger={['click']}
side="bottom"
align="end"
>
<Button
type="text"
@@ -727,7 +726,7 @@ function DashboardsList(): JSX.Element {
>
New Dashboard
</Button>
</Dropdown>
</DropdownMenuSimple>
<Button
type="text"
className="learn-more"
@@ -756,11 +755,11 @@ function DashboardsList(): JSX.Element {
onChange={handleSearch}
/>
{createNewDashboard && (
<Dropdown
overlayClassName="new-dashboard-menu"
<DropdownMenuSimple
className="new-dashboard-menu"
menu={{ items: getCreateDashboardItems }}
placement="bottomRight"
trigger={['click']}
side="bottom"
align="end"
>
<Button
type="primary"
@@ -773,7 +772,7 @@ function DashboardsList(): JSX.Element {
>
New dashboard
</Button>
</Dropdown>
</DropdownMenuSimple>
)}
</div>

View File

@@ -7,7 +7,12 @@ import {
DropResult,
} from 'react-beautiful-dnd';
import { Color } from '@signozhq/design-tokens';
import { Button, Divider, Dropdown, Input, MenuProps, Tooltip } from 'antd';
import { Button, Divider, Input, Tooltip } from 'antd';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from '@signozhq/ui/dropdown-menu';
import { Typography } from '@signozhq/ui/typography';
import { FieldDataType } from 'api/v5/v5';
import { SOMETHING_WENT_WRONG } from 'constants/api';
@@ -159,34 +164,12 @@ function ExplorerColumnsRenderer({
debouncedSetQuerySearchText(e.target.value);
};
const items: MenuProps['items'] = [
{
key: 'search',
label: (
<Input
type="text"
placeholder="Search"
className="explorer-columns-search"
value={searchText}
onChange={handleSearchChange}
prefix={<Search size={16} style={{ padding: '6px' }} />}
/>
),
},
{
key: 'columns',
label: (
<ExplorerAttributeColumns
isLoading={isLoading}
data={data}
searchText={searchText}
isAttributeKeySelected={isAttributeKeySelected}
handleCheckboxChange={handleCheckboxChange}
dataSource={initialDataSource}
/>
),
},
];
const handleOpenChange = (nextOpen: boolean): void => {
setOpen(nextOpen);
if (nextOpen) {
setSearchText('');
}
};
const removeSelectedLogField = (name: string): void => {
if (
@@ -238,13 +221,6 @@ function ExplorerColumnsRenderer({
}
};
const toggleDropdown = (): void => {
setOpen(!open);
if (!open) {
setSearchText('');
}
};
const isDarkMode = useIsDarkMode();
return (
@@ -327,25 +303,38 @@ function ExplorerColumnsRenderer({
</Droppable>
</DragDropContext>
<div>
<Dropdown
menu={{ items }}
arrow
placement="top"
open={open}
overlayClassName="explorer-columns-dropdown"
>
<Button
className="action-btn"
data-testid="add-columns-button"
icon={
<CirclePlus
size={16}
color={isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100}
/>
}
onClick={toggleDropdown}
/>
</Dropdown>
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
<DropdownMenuTrigger asChild>
<Button
className="action-btn"
data-testid="add-columns-button"
icon={
<CirclePlus
size={16}
color={isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100}
/>
}
/>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" className="explorer-columns-dropdown">
<Input
type="text"
placeholder="Search"
className="explorer-columns-search"
value={searchText}
onChange={handleSearchChange}
prefix={<Search size={16} style={{ padding: '6px' }} />}
/>
<ExplorerAttributeColumns
isLoading={isLoading}
data={data}
searchText={searchText}
isAttributeKeySelected={isAttributeKeySelected}
handleCheckboxChange={handleCheckboxChange}
dataSource={initialDataSource}
/>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)}

View File

@@ -1,6 +1,7 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { ChevronDown } from '@signozhq/icons';
import { Button, ColorPicker, Dropdown, MenuProps, Space } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import { Button, ColorPicker, Space } from 'antd';
import type { Color } from 'antd/es/color-picker';
import useDebounce from 'hooks/useDebounce';
@@ -26,7 +27,7 @@ function ColorSelector({
setColorFromPicker(hex);
};
const items: MenuProps['items'] = [
const items: MenuItem[] = [
{
key: 'Red',
label: <CustomColor color="Red" />,
@@ -62,7 +63,7 @@ function ColorSelector({
];
return (
<Dropdown menu={{ items }} trigger={['click']}>
<DropdownMenuSimple menu={{ items }}>
<Button
onClick={(e): void => e.preventDefault()}
className="color-selector-button"
@@ -72,7 +73,7 @@ function ColorSelector({
<ChevronDown size="md" />
</Space>
</Button>
</Dropdown>
</DropdownMenuSimple>
);
}

View File

@@ -1,5 +1,6 @@
import {
MouseEvent,
ReactNode,
useCallback,
useEffect,
useMemo,
@@ -25,7 +26,14 @@ import {
verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { Button, Dropdown, MenuProps, Modal, Tooltip } from 'antd';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@signozhq/ui/dropdown-menu';
import { Button, MenuProps, Modal, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent';
import { Logout } from 'api/utils';
import updateUserPreference from 'api/v1/user/preferences/name/update';
@@ -1182,46 +1190,95 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
{isAIAssistantEnabled && renderNavItems([aiAssistantMenuItem], false)}
<div className="nav-dropdown-item">
<Dropdown
menu={{
items: helpSupportDropdownMenuItems,
onClick: handleHelpSupportMenuItemClick,
}}
placement="topLeft"
overlayClassName="nav-dropdown-overlay help-support-dropdown"
trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
>
<div className="nav-item">
<div className="nav-item-data" data-testid="help-support-nav-item">
<div className="nav-item-icon">{helpSupportMenuItem.icon}</div>
<DropdownMenu onOpenChange={(open): void => setIsDropdownOpen(open)}>
<DropdownMenuTrigger asChild>
<div className="nav-item">
<div className="nav-item-data" data-testid="help-support-nav-item">
<div className="nav-item-icon">{helpSupportMenuItem.icon}</div>
<div className="nav-item-label">{helpSupportMenuItem.label}</div>
<div className="nav-item-label">{helpSupportMenuItem.label}</div>
</div>
</div>
</div>
</Dropdown>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="nav-dropdown-overlay help-support-dropdown"
>
{helpSupportDropdownMenuItems.map((item, idx) => {
if ('type' in item) {
// eslint-disable-next-line react/no-array-index-key
return <DropdownMenuSeparator key={`help-sep-${idx}`} />;
}
return (
<DropdownMenuItem
key={String(item.key)}
leftIcon={item.icon}
onClick={(e): void =>
handleHelpSupportMenuItemClick({
...item,
key: String(item.key),
domEvent: e.nativeEvent,
} as unknown as SidebarItem)
}
>
{item.label}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
<div className="nav-dropdown-item">
<Dropdown
menu={{
items: userSettingsDropdownMenuItems,
onClick: handleSettingsMenuItemClick,
}}
placement="topLeft"
overlayClassName="nav-dropdown-overlay settings-dropdown"
trigger={['click']}
onOpenChange={(open): void => setIsDropdownOpen(open)}
>
<div className={cx('nav-item', isSettingsPage && 'active')}>
<div className="nav-item-active-marker" />
<div className="nav-item-data" data-testid="settings-nav-item">
<div className="nav-item-icon">{userSettingsMenuItem.icon}</div>
<DropdownMenu onOpenChange={(open): void => setIsDropdownOpen(open)}>
<DropdownMenuTrigger asChild>
<div className={cx('nav-item', isSettingsPage && 'active')}>
<div className="nav-item-active-marker" />
<div className="nav-item-data" data-testid="settings-nav-item">
<div className="nav-item-icon">{userSettingsMenuItem.icon}</div>
<div className="nav-item-label">{userSettingsMenuItem.label}</div>
<div className="nav-item-label">{userSettingsMenuItem.label}</div>
</div>
</div>
</div>
</Dropdown>
</DropdownMenuTrigger>
<DropdownMenuContent
side="top"
align="start"
className="nav-dropdown-overlay settings-dropdown"
>
{(userSettingsDropdownMenuItems ?? []).map((item, idx) => {
if (!item) {
return null;
}
if ('type' in item && item.type === 'divider') {
// eslint-disable-next-line react/no-array-index-key
return <DropdownMenuSeparator key={`settings-sep-${idx}`} />;
}
const settingsItem = item as {
key?: string | number;
label?: ReactNode;
icon?: ReactNode;
disabled?: boolean;
};
return (
<DropdownMenuItem
key={String(settingsItem.key)}
leftIcon={settingsItem.icon}
disabled={settingsItem.disabled}
onClick={(e): void =>
handleSettingsMenuItemClick({
key: String(settingsItem.key),
domEvent: e.nativeEvent,
} as unknown as SidebarItem)
}
>
{settingsItem.label}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Divider, Dropdown, MenuProps, Switch, Tooltip } from 'antd';
import { Divider, Switch, Tooltip } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Copy, Ellipsis, PenLine, Trash2 } from '@signozhq/icons';
import {
@@ -11,7 +12,6 @@ import {
} from 'pages/AlertDetails/hooks';
import CopyToClipboard from 'periscope/components/CopyToClipboard';
import { useAlertRule } from 'providers/Alert';
import { CSSProperties } from 'styled-components';
import { NEW_ALERT_SCHEMA_VERSION } from 'types/api/alerts/alertTypesV2';
import { AlertDef } from 'types/api/alerts/def';
@@ -20,16 +20,6 @@ import RenameModal from './RenameModal';
import './ActionButtons.styles.scss';
const menuItemStyle: CSSProperties = {
fontSize: '14px',
letterSpacing: '0.14px',
};
const menuItemStyleV2: CSSProperties = {
fontSize: '13px',
letterSpacing: '0.13px',
};
function AlertActionButtons({
ruleId,
alertDetails,
@@ -68,9 +58,7 @@ function AlertActionButtons({
const isV2Alert = alertDetails.schemaVersion === NEW_ALERT_SCHEMA_VERSION;
const finalMenuItemStyle = isV2Alert ? menuItemStyleV2 : menuItemStyle;
const menuItems: MenuProps['items'] = [
const menuItems: MenuItem[] = [
...(!isV2Alert
? [
{
@@ -78,7 +66,6 @@ function AlertActionButtons({
label: 'Rename',
icon: <PenLine size={16} color={Color.BG_VANILLA_400} />,
onClick: handleRename,
style: finalMenuItemStyle,
},
]
: []),
@@ -87,17 +74,13 @@ function AlertActionButtons({
label: 'Duplicate',
icon: <Copy size={16} color={Color.BG_VANILLA_400} />,
onClick: handleAlertDuplicate,
style: finalMenuItemStyle,
},
{
key: 'delete-rule',
label: 'Delete',
icon: <Trash2 size={16} color={Color.BG_CHERRY_400} />,
onClick: handleAlertDelete,
style: {
...finalMenuItemStyle,
color: Color.BG_CHERRY_400,
},
danger: true,
},
];
@@ -138,7 +121,7 @@ function AlertActionButtons({
<Divider type="vertical" />
<Dropdown trigger={['click']} menu={{ items: menuItems }}>
<DropdownMenuSimple menu={{ items: menuItems }}>
<Tooltip title="More options">
<Ellipsis
size={16}
@@ -147,7 +130,7 @@ function AlertActionButtons({
className="dropdown-icon"
/>
</Tooltip>
</Dropdown>
</DropdownMenuSimple>
</div>
<RenameModal

View File

@@ -1,14 +1,6 @@
import { useMemo, useState } from 'react';
import {
Button,
Divider,
Dropdown,
Form,
MenuProps,
Space,
Switch,
Tooltip,
} from 'antd';
import { Button, Divider, Form, Space, Switch, Tooltip } from 'antd';
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
import cx from 'classnames';
import { FilterSelect } from 'components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions';
import { QueryParams } from 'constants/query';
@@ -44,16 +36,22 @@ function FunnelStep({
const [isAddDetailsModalOpen, setIsAddDetailsModalOpen] =
useState<boolean>(false);
const latencyPointerItems: MenuProps['items'] = LatencyPointers.map(
(option) => ({
key: option.value,
label: option.key,
style:
option.value === stepData.latency_pointer
? { backgroundColor: 'var(--bg-slate-100)' }
: {},
}),
);
const latencyPointerItems: MenuItem[] = [
{
type: 'radio-group',
value: stepData.latency_pointer,
onChange: (value): void =>
onStepChange(index, {
latency_pointer: value as FunnelStepData['latency_pointer'],
}),
children: LatencyPointers.map((option) => ({
type: 'radio',
key: option.value,
label: option.key,
value: option.value,
})),
},
];
const updatedCurrentQuery = useMemo(
() => ({
@@ -212,17 +210,18 @@ function FunnelStep({
</div>
<div className="latency-pointer">
<div className="latency-pointer__label">Latency pointer</div>
<Dropdown
menu={{
items: latencyPointerItems,
onClick: ({ key }): void =>
onStepChange(index, {
latency_pointer: key as FunnelStepData['latency_pointer'],
}),
}}
trigger={['click']}
disabled={!hasEditPermission}
>
{hasEditPermission ? (
<DropdownMenuSimple menu={{ items: latencyPointerItems }}>
<Space>
{
LatencyPointers.find(
(option) => option.value === stepData.latency_pointer,
)?.key
}
<ChevronDown size={14} color="var(--bg-vanilla-400)" />
</Space>
</DropdownMenuSimple>
) : (
<Space>
{
LatencyPointers.find(
@@ -231,7 +230,7 @@ function FunnelStep({
}
<ChevronDown size={14} color="var(--bg-vanilla-400)" />
</Space>
</Dropdown>
)}
</div>
</div>
</Form>

View File

@@ -17,8 +17,6 @@ from fixtures.logger import setup_logger
logger = setup_logger(__name__)
ZEUS_NETWORK_ALIAS = "signoz-zeus"
@pytest.fixture(name="zeus", scope="package")
def zeus(
@@ -33,7 +31,6 @@ def zeus(
def create() -> types.TestContainerDocker:
container = WireMockContainer(image="wiremock/wiremock:2.35.1-1", secure=False)
container.with_network(network)
container.with_network_aliases(ZEUS_NETWORK_ALIAS)
container.start()
return types.TestContainerDocker(
@@ -45,7 +42,7 @@ def zeus(
container.get_exposed_port(8080),
)
},
container_configs={"8080": types.TestContainerUrlConfig("http", ZEUS_NETWORK_ALIAS, 8080)},
container_configs={"8080": types.TestContainerUrlConfig("http", container.get_wrapped_container().name, 8080)},
)
def delete(container: types.TestContainerDocker):

View File

@@ -1,19 +1,14 @@
import os
import platform
import shutil
import subprocess
import threading
import time
from dataclasses import dataclass
from http import HTTPStatus
from os import path
from pathlib import Path
import docker
import docker.errors
import pytest
import requests
from testcontainers.core.container import DockerContainer, Network
from testcontainers.core.image import DockerImage
from fixtures import reuse, types
from fixtures.logger import setup_logger
@@ -21,110 +16,6 @@ from fixtures.logger import setup_logger
logger = setup_logger(__name__)
@dataclass
class SigNozImageBuild:
process: subprocess.Popen
command: list[str]
cache_path: Path | None = None
next_cache_path: Path | None = None
reader: threading.Thread | None = None
def _stream_build_output(pipe, log) -> None:
try:
for line in iter(pipe.readline, ""):
log.info("buildx: %s", line.rstrip())
finally:
pipe.close()
def start_signoz_image_build(pytestconfig: pytest.Config, dockerfile_path: str, arch: str, zeus_url: str) -> SigNozImageBuild:
root = pytestconfig.rootpath.parent
command = [
"docker",
"buildx",
"build",
"--load",
"--progress",
"plain",
"--tag",
"signoz:integration",
"--file",
dockerfile_path,
"--build-arg",
f"TARGETARCH={arch}",
"--build-arg",
f"ZEUSURL={zeus_url}",
str(root),
]
cache_path = None
next_cache_path = None
if os.environ.get("ACTIONS_RUNTIME_TOKEN"):
# Running in GitHub Actions — use BuildKit's native GHA cache backend.
# Avoids the local-write races and partial exports seen with type=local.
scope = os.environ.get("SIGNOZ_BUILDX_GHA_SCOPE", "signoz-integration")
command.extend(["--cache-from", f"type=gha,scope={scope}"])
command.extend(["--cache-to", f"type=gha,scope={scope},mode=max"])
elif build_cache_dir := os.environ.get("SIGNOZ_INTEGRATION_BUILD_CACHE_DIR"):
# Local cache for developer machines / non-GHA CI.
cache_path = Path(build_cache_dir)
next_cache_path = Path(f"{build_cache_dir}-next")
cache_path.parent.mkdir(parents=True, exist_ok=True)
shutil.rmtree(next_cache_path, ignore_errors=True)
if cache_path.exists():
command.extend(["--cache-from", f"type=local,src={cache_path}"])
command.extend(["--cache-to", f"type=local,dest={next_cache_path},mode=max"])
logger.info("Building SigNoz integration image with %s", " ".join(command))
process = subprocess.Popen(
command,
cwd=root,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
)
reader = threading.Thread(target=_stream_build_output, args=(process.stdout, logger), daemon=True)
reader.start()
return SigNozImageBuild(
process=process,
command=command,
cache_path=cache_path,
next_cache_path=next_cache_path,
reader=reader,
)
def wait_for_signoz_image_build(build: SigNozImageBuild) -> None:
returncode = build.process.wait()
if build.reader is not None:
build.reader.join(timeout=5)
if returncode != 0:
raise subprocess.CalledProcessError(returncode, build.command)
if build.cache_path and build.next_cache_path and build.next_cache_path.exists():
shutil.rmtree(build.cache_path, ignore_errors=True)
shutil.move(build.next_cache_path, build.cache_path)
def stop_signoz_image_build(build: SigNozImageBuild) -> None:
if build.process.poll() is not None:
return
build.process.terminate()
try:
build.process.wait(timeout=10)
except subprocess.TimeoutExpired:
build.process.kill()
build.process.wait()
if build.reader is not None:
build.reader.join(timeout=5)
def create_signoz(
network: Network,
zeus: types.TestContainerDocker,
@@ -142,6 +33,9 @@ def create_signoz(
"""
def create() -> types.SigNoz:
# Run the migrations for clickhouse
request.getfixturevalue("migrator")
# Get the no-web flag
with_web = pytestconfig.getoption("--with-web")
@@ -154,15 +48,19 @@ def create_signoz(
if with_web:
dockerfile_path = "cmd/enterprise/Dockerfile.with-web.integration"
# The SigNoz image build does not depend on ClickHouse migrations, so
# build it while the migrator container runs.
image_build = start_signoz_image_build(pytestconfig, dockerfile_path, arch, zeus.container_configs["8080"].base())
try:
request.getfixturevalue("migrator")
wait_for_signoz_image_build(image_build)
except Exception: # pylint: disable=broad-exception-caught
stop_signoz_image_build(image_build)
raise
# Docker build context is the repo root — one up from pytest's
# rootdir (tests/).
self = DockerImage(
path=str(pytestconfig.rootpath.parent),
dockerfile_path=dockerfile_path,
tag="signoz:integration",
buildargs={
"TARGETARCH": arch,
"ZEUSURL": zeus.container_configs["8080"].base(),
},
)
self.build()
env = (
{