mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-27 19:40:41 +00:00
Compare commits
4 Commits
main
...
chore/save
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37ce4471ff | ||
|
|
f4922e2bc5 | ||
|
|
bd11bb01e7 | ||
|
|
0921f90772 |
@@ -46,9 +46,9 @@ One endpoint feeding several widgets stays one handler that reads the request. `
|
||||
|
||||
```ts
|
||||
rest.get(
|
||||
'http://localhost/api/v1/explorer/views',
|
||||
'http://localhost/api/v2/saved_views',
|
||||
response.json((req) =>
|
||||
savedViews(values.savedViews, req.url.searchParams.get('sourcePage') ?? 'logs'),
|
||||
savedViews(values.savedViews, req.url.searchParams.get('source') ?? 'logs'),
|
||||
),
|
||||
),
|
||||
```
|
||||
|
||||
@@ -140,7 +140,7 @@ const preview: Preview = {
|
||||
'Services',
|
||||
['List', 'Detail', 'Top Level Operations', 'Service Map'],
|
||||
'Logs',
|
||||
['Explorer', 'Live Tail', 'Saved Views', 'Pipelines', 'Settings'],
|
||||
['Explorer', 'Live Tail', 'Pipelines', 'Settings'],
|
||||
'Traces',
|
||||
['Explorer', 'Trace Details', 'Funnel Details'],
|
||||
'Metrics',
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
@@ -47,7 +47,6 @@
|
||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
|
||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer Views",
|
||||
"METER": "SigNoz | Meter",
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view",
|
||||
"delete_confirm_message": "Are you sure you want to delete {{viewName}} view? Deleting a view is irreversible and cannot be undone."
|
||||
}
|
||||
@@ -50,8 +50,6 @@
|
||||
"WORKSPACE_SUSPENDED": "SigNoz | Workspace Suspended",
|
||||
"WORKSPACE_ACCESS_RESTRICTED": "SigNoz | Workspace Access Restricted",
|
||||
"SUPPORT": "SigNoz | Support",
|
||||
"LOGS_SAVE_VIEWS": "SigNoz | Logs Saved Views",
|
||||
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
||||
"TRACES_FUNNELS": "SigNoz | Traces Funnels",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz",
|
||||
"SHORTCUTS": "SigNoz | Shortcuts",
|
||||
@@ -66,10 +64,8 @@
|
||||
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
|
||||
"METRICS_EXPLORER": "SigNoz | Metrics Explorer",
|
||||
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
|
||||
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
|
||||
"API_MONITORING": "SigNoz | External APIs",
|
||||
"METER_EXPLORER": "SigNoz | Meter Explorer",
|
||||
"METER_EXPLORER_VIEWS": "SigNoz | Meter Explorer Views",
|
||||
"METER": "SigNoz | Meter",
|
||||
"ROLES_SETTINGS": "SigNoz | Roles",
|
||||
"MEMBERS_SETTINGS": "SigNoz | Members",
|
||||
|
||||
@@ -303,10 +303,10 @@ describe('PrivateRoute', () => {
|
||||
it.each([
|
||||
['/logs-save-views', ROUTES.LOGS_EXPLORER],
|
||||
['/traces-save-views', ROUTES.TRACES_EXPLORER],
|
||||
[ROUTES.LOGS_SAVE_VIEWS, ROUTES.LOGS_EXPLORER],
|
||||
[ROUTES.TRACES_SAVE_VIEWS, ROUTES.TRACES_EXPLORER],
|
||||
[ROUTES.METRICS_EXPLORER_VIEWS, ROUTES.METRICS_EXPLORER_EXPLORER],
|
||||
[ROUTES.METER_EXPLORER_VIEWS, ROUTES.METER_EXPLORER],
|
||||
['/logs/saved-views', ROUTES.LOGS_EXPLORER],
|
||||
['/traces/saved-views', ROUTES.TRACES_EXPLORER],
|
||||
['/metrics-explorer/views', ROUTES.METRICS_EXPLORER_EXPLORER],
|
||||
['/meter/explorer/views', ROUTES.METER_EXPLORER],
|
||||
])('should redirect the old views route %s to %s', (from, to) => {
|
||||
renderPrivateRoute({ initialRoute: from });
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ import AppLayout from 'container/AppLayout';
|
||||
import Hex from 'crypto-js/enc-hex';
|
||||
import HmacSHA256 from 'crypto-js/hmac-sha256';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
import { ChatSupportState, useChatSupport } from 'hooks/useChatSupport';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
@@ -66,7 +65,6 @@ function App(): JSX.Element {
|
||||
} = useAppContext();
|
||||
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
const isSavedViewEnabled = useSavedViewEnabled();
|
||||
const chatSupport = useChatSupport();
|
||||
|
||||
const { hostname } = window.location;
|
||||
@@ -256,18 +254,8 @@ function App(): JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
// The bottom strip carries Support, so the floating bubble goes entirely.
|
||||
if (
|
||||
isSavedViewEnabled ||
|
||||
pathname === ROUTES.ONBOARDING ||
|
||||
pathname.startsWith('/public/dashboard/') ||
|
||||
pathname === '/ai-assistant' ||
|
||||
pathname.startsWith('/ai-assistant/')
|
||||
) {
|
||||
window.Pylon?.('hideChatBubble');
|
||||
} else {
|
||||
window.Pylon?.('showChatBubble');
|
||||
}
|
||||
}, [pathname, isSavedViewEnabled]);
|
||||
window.Pylon?.('hideChatBubble');
|
||||
}, [pathname]);
|
||||
|
||||
// Identity for the Pylon widget. Whether this user gets Pylon at all is
|
||||
// `useChatSupport`'s call — this only fills in who they are.
|
||||
|
||||
@@ -26,10 +26,6 @@ export const ServiceMapPage = Loadable(
|
||||
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
|
||||
);
|
||||
|
||||
export const LogsSaveViews = Loadable(
|
||||
() => import(/* webpackChunkName: "LogsSaveViews" */ 'pages/LogsModulePage'), // TODO: Add a wrapper so that the same component can be used in traces
|
||||
);
|
||||
|
||||
export const TracesExplorer = Loadable(
|
||||
() =>
|
||||
import(
|
||||
@@ -37,11 +33,6 @@ export const TracesExplorer = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TracesSaveViews = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "Traces Save Views" */ 'pages/TracesModulePage'),
|
||||
);
|
||||
|
||||
export const TracesFunnels = Loadable(
|
||||
() =>
|
||||
import(/* webpackChunkName: "Traces Funnels" */ 'pages/TracesModulePage'),
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
Login,
|
||||
Logs,
|
||||
LogsIndexToFields,
|
||||
LogsSaveViews,
|
||||
MessagingQueuesMainPage,
|
||||
MeterExplorerPage,
|
||||
MetricsExplorer,
|
||||
@@ -48,7 +47,6 @@ import {
|
||||
TracesExplorer,
|
||||
TracesFunnelDetails,
|
||||
TracesFunnels,
|
||||
TracesSaveViews,
|
||||
UnAuthorized,
|
||||
UsageExplorerPage,
|
||||
WorkspaceAccessRestricted,
|
||||
@@ -120,13 +118,6 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
key: 'SERVICE_MAP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGS_SAVE_VIEWS,
|
||||
component: LogsSaveViews,
|
||||
isPrivate: true,
|
||||
exact: true,
|
||||
key: 'LOGS_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
@@ -218,13 +209,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'TRACES_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_SAVE_VIEWS,
|
||||
exact: true,
|
||||
component: TracesSaveViews,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_FUNNELS,
|
||||
exact: true,
|
||||
@@ -414,13 +398,6 @@ const routes: AppRoutes[] = [
|
||||
key: 'METRICS_EXPLORER_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_VIEWS,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_VIEWS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_VOLUME_CONTROL,
|
||||
exact: true,
|
||||
@@ -443,13 +420,6 @@ const routes: AppRoutes[] = [
|
||||
key: 'METER_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METER_EXPLORER_VIEWS,
|
||||
exact: true,
|
||||
component: MeterExplorerPage,
|
||||
key: 'METER_EXPLORER_VIEWS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.API_MONITORING,
|
||||
exact: true,
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { DeleteViewPayloadProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const deleteView = (uuid: string): Promise<DeleteViewPayloadProps> =>
|
||||
axios.delete(`/explorer/views/${uuid}`);
|
||||
@@ -1,9 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { AllViewsProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const getAllViews = (
|
||||
sourcepage: DataSource | 'meter',
|
||||
): Promise<AxiosResponse<AllViewsProps>> =>
|
||||
axios.get(`/explorer/views?sourcePage=${sourcepage}`);
|
||||
@@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
|
||||
/**
|
||||
* Fetches a single saved view by ID (`GET /api/v1/explorer/views/{viewId}`).
|
||||
*
|
||||
* Hand-maintained alongside the other `api/saveView/*` clients — explorer views
|
||||
* are not in `docs/api/openapi.yml`, so Orval does not generate a hook here
|
||||
* (unlike e.g. `useGetChannelByID` under `api/generated/services/channels`).
|
||||
*
|
||||
* Used by the AI assistant "Open view" action to load `compositeQuery` and
|
||||
* navigate to the correct explorer without listing every view per source page.
|
||||
* See `container/AIAssistant/components/ActionsSection/utils/openSavedView.ts`.
|
||||
*/
|
||||
export interface GetViewByIdProps {
|
||||
status: string;
|
||||
data: ViewProps;
|
||||
}
|
||||
|
||||
export const getViewById = (
|
||||
viewKey: string,
|
||||
): Promise<AxiosResponse<GetViewByIdProps>> =>
|
||||
axios.get(`/explorer/views/${viewKey}`);
|
||||
@@ -1,16 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const saveView = ({
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
viewName,
|
||||
extraData,
|
||||
}: SaveViewProps): Promise<AxiosResponse<SaveViewPayloadProps>> =>
|
||||
axios.post('/explorer/views', {
|
||||
name: viewName,
|
||||
sourcePage,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
UpdateViewPayloadProps,
|
||||
UpdateViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
|
||||
export const updateView = ({
|
||||
compositeQuery,
|
||||
viewName,
|
||||
extraData,
|
||||
sourcePage,
|
||||
viewKey,
|
||||
}: UpdateViewProps): Promise<UpdateViewPayloadProps> =>
|
||||
axios.put(`/explorer/views/${viewKey}`, {
|
||||
name: viewName,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
sourcePage,
|
||||
});
|
||||
@@ -1,43 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button } from 'antd';
|
||||
import AddCreditCardModal from 'components/AddCreditCardModal/AddCreditCardModal';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { MessageSquareText } from '@signozhq/icons';
|
||||
|
||||
export default function ChatSupportGateway(): JSX.Element {
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="chat-support-gateway">
|
||||
<Button
|
||||
className="chat-support-gateway-btn"
|
||||
onClick={(): void => {
|
||||
logEvent('Disabled Chat Support: Clicked', {
|
||||
source: `chat support icon`,
|
||||
page: pathname,
|
||||
});
|
||||
|
||||
setIsAddCreditCardModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<MessageSquareText size={24} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AddCreditCardModal
|
||||
open={isAddCreditCardModalOpen}
|
||||
onClose={(): void => setIsAddCreditCardModalOpen(false)}
|
||||
onAddCreditCard={(): void => {
|
||||
logEvent('Add Credit card modal: Clicked', {
|
||||
source: `chat support icon`,
|
||||
page: pathname,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button, Col, Popover, Row, Select, Space } from 'antd';
|
||||
import { Dropdown, type DropdownItemType } from '@signozhq/ui/dropdown';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import axios from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { useGetSavedViewParams } from 'hooks/saveViews/useGetSavedViewParams';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
|
||||
import { useUpdateView } from 'hooks/saveViews/useUpdateView';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
|
||||
import MenuItemGenerator from './MenuItemGenerator';
|
||||
import SaveViewWithName from './SaveViewWithName';
|
||||
import {
|
||||
DropDownOverlay,
|
||||
ExplorerCardHeadContainer,
|
||||
OffSetCol,
|
||||
} from './styles';
|
||||
import { ExplorerCardProps } from './types';
|
||||
import { deleteViewHandler } from './utils';
|
||||
import { Ellipsis, Save, Share2, Trash2 } from '@signozhq/icons';
|
||||
|
||||
function ExplorerCard({
|
||||
sourcepage,
|
||||
children,
|
||||
}: ExplorerCardProps): JSX.Element {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [, setCopyUrl] = useCopyToClipboard();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCopyUrlHandler = (): void => {
|
||||
setCopyUrl(window.location.href);
|
||||
notifications.success({
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
isStagedQueryUpdated,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const {
|
||||
data: viewsData,
|
||||
isLoading,
|
||||
error,
|
||||
isRefetching,
|
||||
refetch: refetchAllView,
|
||||
} = useGetAllViews(sourcepage);
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const handleOpenChange = (newOpen = false): void => {
|
||||
setIsOpen(newOpen);
|
||||
};
|
||||
|
||||
const { viewName, viewKey } = useGetSavedViewParams();
|
||||
|
||||
const { options } = useOptionsMenu({
|
||||
storageKey:
|
||||
sourcepage === DataSource.TRACES
|
||||
? LOCALSTORAGE.TRACES_LIST_OPTIONS
|
||||
: LOCALSTORAGE.LOGS_LIST_OPTIONS,
|
||||
dataSource: sourcepage,
|
||||
aggregateOperator: StringOperators.NOOP,
|
||||
});
|
||||
|
||||
const isQueryUpdated = isStagedQueryUpdated(
|
||||
viewsData?.data?.data,
|
||||
viewKey,
|
||||
options,
|
||||
);
|
||||
|
||||
const { mutateAsync: updateViewAsync } = useUpdateView({
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(viewKey);
|
||||
|
||||
const showErrorNotification = (err: Error): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteHandler = (): void =>
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: viewKey,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage: sourcepage,
|
||||
});
|
||||
|
||||
const onUpdateQueryHandler = (): void => {
|
||||
updateViewAsync(
|
||||
{
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'View Updated Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(err);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const moreOptionItems: DropdownItemType[] = [
|
||||
{
|
||||
type: 'item',
|
||||
value: 'delete',
|
||||
label: <Typography.Text strong>Delete</Typography.Text>,
|
||||
onClick: onDeleteHandler,
|
||||
prefix: <Trash2 size="md" />,
|
||||
},
|
||||
];
|
||||
|
||||
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
|
||||
const saveButtonIcon = isQueryUpdated ? null : <Save size="md" />;
|
||||
|
||||
const showSaveView = false;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showSaveView && (
|
||||
<ExplorerCardHeadContainer size="small">
|
||||
<Row align="middle">
|
||||
<Col span={6}>
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url={ExploreHeaderToolTip.url}
|
||||
text={ExploreHeaderToolTip.text}
|
||||
useFilledIcon={false}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<OffSetCol span={18}>
|
||||
<Space size="large">
|
||||
{viewsData?.data.data && viewsData?.data.data.length && (
|
||||
<Space>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
loading={isLoading || isRefetching}
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
dropdownStyle={DropDownOverlay}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp="value"
|
||||
value={viewName || undefined}
|
||||
>
|
||||
{viewsData?.data.data.map((view) => (
|
||||
<Select.Option key={view.id} value={view.name}>
|
||||
<MenuItemGenerator
|
||||
viewName={view.name}
|
||||
viewKey={viewKey}
|
||||
createdBy={view.createdBy}
|
||||
uuid={view.id}
|
||||
refetchAllView={refetchAllView}
|
||||
viewData={viewsData.data.data}
|
||||
sourcePage={sourcepage}
|
||||
/>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
)}
|
||||
{isQueryUpdated && (
|
||||
<Button type="primary" icon={<Save />} onClick={onUpdateQueryHandler}>
|
||||
Save changes
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
content={
|
||||
<SaveViewWithName
|
||||
sourcePage={sourcepage}
|
||||
handlePopOverClose={handleOpenChange}
|
||||
refetchAllView={refetchAllView}
|
||||
/>
|
||||
}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<Button
|
||||
type={saveButtonType}
|
||||
icon={saveButtonIcon}
|
||||
data-testid="traces-save-view-action"
|
||||
>
|
||||
{isQueryUpdated
|
||||
? SaveButtonText.SAVE_AS_NEW_VIEW
|
||||
: SaveButtonText.SAVE_VIEW}
|
||||
</Button>
|
||||
</Popover>
|
||||
<Share2 onClick={onCopyUrlHandler} size="md" />
|
||||
{viewKey && (
|
||||
<Dropdown
|
||||
items={moreOptionItems}
|
||||
nativeButton
|
||||
align="end"
|
||||
side="bottom"
|
||||
>
|
||||
<Button type="text" size="small" icon={<Ellipsis size="md" />} />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
</Row>
|
||||
</ExplorerCardHeadContainer>
|
||||
)}
|
||||
|
||||
<div>{children}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExplorerCard;
|
||||
@@ -1,103 +0,0 @@
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
import { Trash2 } from '@signozhq/icons';
|
||||
import { Col, Row, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
|
||||
import { MenuItemContainer } from './styles';
|
||||
import { MenuItemLabelGeneratorProps } from './types';
|
||||
import {
|
||||
deleteViewHandler,
|
||||
getViewDetailsUsingViewKey,
|
||||
trimViewName,
|
||||
} from './utils';
|
||||
|
||||
function MenuItemGenerator({
|
||||
viewName,
|
||||
viewKey,
|
||||
createdBy,
|
||||
uuid,
|
||||
viewData,
|
||||
refetchAllView,
|
||||
sourcePage,
|
||||
}: MenuItemLabelGeneratorProps): JSX.Element {
|
||||
const { panelType, redirectWithQueryBuilderData, updateAllQueriesOperators } =
|
||||
useQueryBuilder();
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(uuid);
|
||||
|
||||
const onDeleteHandler = (event: MouseEvent<SVGSVGElement>): void => {
|
||||
event.stopPropagation();
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: uuid,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
});
|
||||
};
|
||||
|
||||
const onMenuItemSelectHandler = useCallback(
|
||||
({ key }: { key: string }): void => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(key, viewData);
|
||||
if (!currentViewDetails) {
|
||||
return;
|
||||
}
|
||||
const { query, name, id, panelType: currentPanelType } = currentViewDetails;
|
||||
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
viewName: name,
|
||||
viewKey: id,
|
||||
});
|
||||
},
|
||||
[viewData, handleExplorerTabChange],
|
||||
);
|
||||
|
||||
const onLabelClickHandler = (): void => {
|
||||
onMenuItemSelectHandler({
|
||||
key: uuid,
|
||||
});
|
||||
};
|
||||
|
||||
const newViewName = trimViewName(viewName);
|
||||
|
||||
return (
|
||||
<MenuItemContainer onClick={onLabelClickHandler}>
|
||||
<Row justify="space-between">
|
||||
<Col span={22}>
|
||||
<Row>
|
||||
<Tooltip title={viewName}>
|
||||
<Typography.Text strong>{newViewName}</Typography.Text>
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row>
|
||||
<Typography.Text color="muted">Created by {createdBy}</Typography.Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Typography.Link>
|
||||
<Trash2
|
||||
role="img"
|
||||
aria-label="Delete view"
|
||||
onClick={onDeleteHandler}
|
||||
size="md"
|
||||
/>
|
||||
</Typography.Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</MenuItemContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default MenuItemGenerator;
|
||||
@@ -1,79 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Card, Form } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSaveView } from 'hooks/saveViews/useSaveView';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
|
||||
import { SaveButton } from './styles';
|
||||
import { SaveViewFormProps, SaveViewWithNameProps } from './types';
|
||||
import { saveViewHandler } from './utils';
|
||||
|
||||
function SaveViewWithName({
|
||||
sourcePage,
|
||||
handlePopOverClose,
|
||||
refetchAllView,
|
||||
}: SaveViewWithNameProps): JSX.Element {
|
||||
const [form] = Form.useForm<SaveViewFormProps>();
|
||||
const { t } = useTranslation(['explorer']);
|
||||
const { currentQuery, panelType, redirectWithQueryBuilderData } =
|
||||
useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||
|
||||
const { isLoading, mutateAsync: saveViewAsync } = useSaveView({
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData: '',
|
||||
});
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
saveViewHandler({
|
||||
compositeQuery,
|
||||
handlePopOverClose,
|
||||
extraData: '',
|
||||
notifications,
|
||||
panelType: panelType || PANEL_TYPES.LIST,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
saveViewAsync,
|
||||
sourcePage,
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
form,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography>{t('name_of_the_view')}</Typography>
|
||||
<Form form={form} onFinish={onSaveHandler} requiredMark>
|
||||
<Form.Item
|
||||
name={['viewName']}
|
||||
required
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please enter view name',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Enter Name" />
|
||||
</Form.Item>
|
||||
<SaveButton
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
loading={isLoading}
|
||||
data-testid="save-view-name-action-button"
|
||||
>
|
||||
Save
|
||||
</SaveButton>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default SaveViewWithName;
|
||||
@@ -1,32 +0,0 @@
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const viewMockData: ViewProps[] = [
|
||||
{
|
||||
id: 'view1',
|
||||
name: 'View 1',
|
||||
createdBy: 'User 1',
|
||||
category: 'category 1',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:31:00.000Z',
|
||||
updatedAt: '2021-07-07T06:33:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 1',
|
||||
},
|
||||
{
|
||||
id: 'view2',
|
||||
name: 'View 2',
|
||||
createdBy: 'User 2',
|
||||
category: 'category 2',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:30:00.000Z',
|
||||
updatedAt: '2021-07-07T06:30:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 2',
|
||||
},
|
||||
];
|
||||
@@ -1,9 +0,0 @@
|
||||
export const ExploreHeaderToolTip = {
|
||||
url: 'https://signoz.io/docs/querying/overview/?utm_source=product&utm_medium=new-query-builder',
|
||||
text: 'More details on how to use query builder',
|
||||
};
|
||||
|
||||
export const SaveButtonText = {
|
||||
SAVE_AS_NEW_VIEW: 'Save as new view',
|
||||
SAVE_VIEW: 'Save view',
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Button, Card, Col } from 'antd';
|
||||
import styled, { CSSProperties } from 'styled-components';
|
||||
|
||||
export const ExplorerCardHeadContainer = styled(Card)`
|
||||
margin: 1rem 0;
|
||||
padding: 0;
|
||||
`;
|
||||
|
||||
export const OffSetCol = styled(Col)`
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
export const SaveButton = styled(Button)`
|
||||
&&& {
|
||||
margin: 1rem 0;
|
||||
width: 5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DropDownOverlay: CSSProperties = {
|
||||
maxHeight: '20rem',
|
||||
overflowY: 'auto',
|
||||
width: '20rem',
|
||||
padding: 0,
|
||||
};
|
||||
|
||||
export const MenuItemContainer = styled(Card)`
|
||||
padding: 0;
|
||||
`;
|
||||
@@ -1,96 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
const historyReplace = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...jest.requireActual('react-router-dom').useHistory(),
|
||||
replace: historyReplace,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): any => ({
|
||||
safeNavigate: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useGetAllViews', () => ({
|
||||
useGetAllViews: jest.fn(() => ({
|
||||
data: { data: { data: viewMockData } },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isRefetching: false,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useUpdateView', () => ({
|
||||
useUpdateView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useDeleteView', () => ({
|
||||
useDeleteView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock usePreferenceSync
|
||||
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
|
||||
usePreferenceSync: (): any => ({
|
||||
preferences: {
|
||||
columns: [],
|
||||
formatting: {
|
||||
maxLines: 1,
|
||||
format: 'table',
|
||||
fontSize: 'small',
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
loading: false,
|
||||
error: null,
|
||||
updateColumns: jest.fn(),
|
||||
updateFormatting: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('ExplorerCard', () => {
|
||||
it('renders a card with a title and a description', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<PreferenceContextProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</PreferenceContextProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.queryByText('Query Builder')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a save view button', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<PreferenceContextProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</PreferenceContextProvider>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.queryByText('Save view')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('antd', () => ({
|
||||
...jest.requireActual('antd'),
|
||||
useForm: jest.fn().mockReturnValue({
|
||||
onFinish: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('MenuItemGenerator', () => {
|
||||
it('should render MenuItemGenerator component', () => {
|
||||
const screen = render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].id}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].id}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(viewMockData[0].name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onMenuItemSelectHandler on click of MenuItemGenerator', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].id}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].id}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
const spanElement = screen.getByRole('img', {
|
||||
name: /delete view/i,
|
||||
});
|
||||
|
||||
expect(spanElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useSaveView', () => ({
|
||||
useSaveView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('SaveViewWithName', () => {
|
||||
it('should render SaveViewWithName component', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call saveViewAsync on click of Save button', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Save'));
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,92 +0,0 @@
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import { FormInstance } from 'antd';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { OptionsQuery } from 'container/OptionsMenu/types';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
DeleteViewPayloadProps,
|
||||
SaveViewPayloadProps,
|
||||
SaveViewProps,
|
||||
ViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
export interface ExplorerCardProps {
|
||||
sourcepage: DataSource;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export type GetViewDetailsUsingViewKey = (
|
||||
viewKey: string,
|
||||
data: ViewProps[] | undefined,
|
||||
) =>
|
||||
| {
|
||||
query: Query;
|
||||
name: string;
|
||||
id: string;
|
||||
panelType: PANEL_TYPES;
|
||||
extraData?: string;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
export interface IsQueryUpdatedInViewProps {
|
||||
viewKey: string;
|
||||
data: ViewProps[] | undefined;
|
||||
stagedQuery: Query | null;
|
||||
currentPanelType: PANEL_TYPES | null;
|
||||
options: OptionsQuery;
|
||||
}
|
||||
|
||||
export interface SaveViewWithNameProps {
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
handlePopOverClose: VoidFunction;
|
||||
refetchAllView: VoidFunction;
|
||||
}
|
||||
|
||||
export interface SaveViewFormProps {
|
||||
viewName: string;
|
||||
}
|
||||
|
||||
export interface MenuItemLabelGeneratorProps {
|
||||
viewName: string;
|
||||
viewKey: string;
|
||||
createdBy: string;
|
||||
uuid: string;
|
||||
viewData: ViewProps[];
|
||||
refetchAllView: VoidFunction;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
|
||||
export interface SaveViewHandlerProps {
|
||||
viewName: string;
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
extraData: string;
|
||||
panelType: PANEL_TYPES | null;
|
||||
notifications: NotificationInstance;
|
||||
refetchAllView: SaveViewWithNameProps['refetchAllView'];
|
||||
saveViewAsync: UseMutateAsyncFunction<
|
||||
AxiosResponse<SaveViewPayloadProps>,
|
||||
Error,
|
||||
SaveViewProps,
|
||||
SaveViewPayloadProps
|
||||
>;
|
||||
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
form: FormInstance<SaveViewFormProps>;
|
||||
}
|
||||
|
||||
export interface DeleteViewHandlerProps {
|
||||
deleteViewAsync: UseMutateAsyncFunction<DeleteViewPayloadProps, Error, string>;
|
||||
refetchAllView: MenuItemLabelGeneratorProps['refetchAllView'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
notifications: NotificationInstance;
|
||||
panelType: PANEL_TYPES | null;
|
||||
viewKey: string;
|
||||
viewId: string;
|
||||
updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'];
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import {
|
||||
DeleteViewHandlerProps,
|
||||
GetViewDetailsUsingViewKey,
|
||||
IsQueryUpdatedInViewProps,
|
||||
SaveViewHandlerProps,
|
||||
} from './types';
|
||||
|
||||
export const showErrorNotification = (
|
||||
notifications: NotificationInstance,
|
||||
err: Error,
|
||||
): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
|
||||
viewKey,
|
||||
data,
|
||||
) => {
|
||||
const selectedView = data?.find((view) => view.id === viewKey);
|
||||
if (selectedView) {
|
||||
const { compositeQuery, name, id, extraData } = selectedView;
|
||||
const query = mapQueryDataFromApi(compositeQuery);
|
||||
return { query, name, id, panelType: compositeQuery.panelType, extraData };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const omitIdFromQuery = (query: Query | null): any => ({
|
||||
...query,
|
||||
builder: {
|
||||
...query?.builder,
|
||||
queryData: query?.builder.queryData.map((queryData) => {
|
||||
const { id: _aggregateAttributeId, ...rest } =
|
||||
queryData.aggregateAttribute || {};
|
||||
const newAggregateAttribute = rest;
|
||||
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
||||
const { id: _groupByAttributeId, ...rest } = groupByAttribute;
|
||||
return rest;
|
||||
});
|
||||
const newItems = queryData.filters?.items?.map((item) => {
|
||||
const { id: _itemId, ...newItem } = item;
|
||||
if (item.key) {
|
||||
const { id: _keyId, ...rest } = item.key;
|
||||
return {
|
||||
...newItem,
|
||||
key: rest,
|
||||
};
|
||||
}
|
||||
return newItem;
|
||||
});
|
||||
return {
|
||||
...queryData,
|
||||
aggregateAttribute: newAggregateAttribute,
|
||||
groupBy: newGroupByAttributes,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: newItems,
|
||||
},
|
||||
limit: queryData.limit ? queryData.limit : 0,
|
||||
offset: queryData.offset ? queryData.offset : 0,
|
||||
pageSize: queryData.pageSize ? queryData.pageSize : 0,
|
||||
};
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
export const isQueryUpdatedInView = ({
|
||||
viewKey,
|
||||
data,
|
||||
stagedQuery,
|
||||
currentPanelType,
|
||||
options,
|
||||
}: IsQueryUpdatedInViewProps): boolean => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(viewKey, data);
|
||||
if (!currentViewDetails) {
|
||||
return false;
|
||||
}
|
||||
const { query, panelType, extraData } = currentViewDetails;
|
||||
|
||||
// Omitting id from aggregateAttribute and groupBy
|
||||
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
|
||||
|
||||
if (
|
||||
updatedCurrentQuery?.builder === undefined ||
|
||||
updatedCurrentQuery.clickhouse_sql === undefined ||
|
||||
updatedCurrentQuery.promql === undefined
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
panelType !== currentPanelType ||
|
||||
!isEqual(query.builder, updatedCurrentQuery?.builder) ||
|
||||
!isEqual(query.clickhouse_sql, updatedCurrentQuery?.clickhouse_sql) ||
|
||||
!isEqual(query.promql, updatedCurrentQuery?.promql) ||
|
||||
!isEqual(
|
||||
options?.selectColumns,
|
||||
extraData && JSON.parse(extraData)?.selectColumns,
|
||||
) ||
|
||||
(stagedQuery?.builder?.queryData?.[0]?.dataSource === DataSource.LOGS &&
|
||||
(!isEqual(options?.format, extraData && JSON.parse(extraData)?.format) ||
|
||||
!isEqual(options?.maxLines, extraData && JSON.parse(extraData)?.maxLines) ||
|
||||
!isEqual(options?.fontSize, extraData && JSON.parse(extraData)?.fontSize)))
|
||||
);
|
||||
};
|
||||
|
||||
export const saveViewHandler = ({
|
||||
saveViewAsync,
|
||||
refetchAllView,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
redirectWithQueryBuilderData,
|
||||
panelType,
|
||||
form,
|
||||
}: SaveViewHandlerProps): void => {
|
||||
saveViewAsync(
|
||||
{
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
refetchAllView();
|
||||
redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), {
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewName]: viewName,
|
||||
[QueryParams.viewKey]: data.data.data,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'View Saved Successfully',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
onSettled: () => {
|
||||
handlePopOverClose();
|
||||
form.resetFields();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteViewHandler = ({
|
||||
deleteViewAsync,
|
||||
refetchAllView,
|
||||
redirectWithQueryBuilderData,
|
||||
notifications,
|
||||
panelType,
|
||||
viewKey,
|
||||
viewId,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
}: DeleteViewHandlerProps): void => {
|
||||
deleteViewAsync(viewKey, {
|
||||
onSuccess: () => {
|
||||
if (viewId === viewKey) {
|
||||
redirectWithQueryBuilderData(
|
||||
updateAllQueriesOperators(
|
||||
initialQueriesMap[sourcePage],
|
||||
panelType || PANEL_TYPES.LIST,
|
||||
sourcePage,
|
||||
),
|
||||
{
|
||||
[QueryParams.viewName]: '',
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewKey]: '',
|
||||
},
|
||||
);
|
||||
}
|
||||
notifications.success({
|
||||
message: 'View Deleted Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const trimViewName = (viewName: string): string => {
|
||||
if (viewName.length > 20) {
|
||||
return `${viewName.substring(0, 20)}...`;
|
||||
}
|
||||
return viewName;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DrawerProps } from 'antd';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import {
|
||||
getBodyDisplayString,
|
||||
getSanitizedLogBody,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import { FontSize } from 'container/OptionsMenu/types';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
@@ -130,7 +130,6 @@ describe('QueryBuilderV2 + QueryV2 - base render', () => {
|
||||
updateAllQueriesOperators,
|
||||
updateQueriesData,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
} as unknown as QueryBuilderContextType;
|
||||
|
||||
|
||||
@@ -136,7 +136,6 @@ describe('MetricsSelect - signal source switching (standalone)', () => {
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
});
|
||||
});
|
||||
@@ -293,7 +292,6 @@ describe('DataSource change - Logs to Traces', () => {
|
||||
updateAllQueriesOperators: ((q: any) => q) as any,
|
||||
updateQueriesData: ((q: any) => q) as any,
|
||||
initQueryBuilderData: jest.fn(),
|
||||
isStagedQueryUpdated: jest.fn(() => false),
|
||||
isDefaultQuery: jest.fn(() => false),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ interface ToggleAction {
|
||||
isOnlyOrAllClicked?: boolean;
|
||||
previousState?: CheckedState;
|
||||
sectionType?: SectionType;
|
||||
quickFilterSource?: QuickFiltersSource;
|
||||
source?: QuickFiltersSource;
|
||||
attributeValues?: string[];
|
||||
}
|
||||
|
||||
@@ -117,8 +117,7 @@ function runToggle(c: ToggleCase): { items: SimpleItem[]; expression: string } {
|
||||
currentQuery: buildQuery(initialItems, initialExpression),
|
||||
activeQueryIndex: 0,
|
||||
filter: { attributeKey: { key: KEY, type: 'tag' } } as never,
|
||||
quickFilterSource:
|
||||
c.action.quickFilterSource ?? QuickFiltersSource.LOGS_EXPLORER,
|
||||
source: c.action.source ?? QuickFiltersSource.LOGS_EXPLORER,
|
||||
attributeValues: c.action.attributeValues ?? ['a', 'b', 'c'],
|
||||
value: c.action.value,
|
||||
checked: c.action.checked,
|
||||
@@ -163,7 +162,7 @@ const TOGGLE_CASES: ToggleCase[] = [
|
||||
action: {
|
||||
value: 'a',
|
||||
checked: false,
|
||||
quickFilterSource: QuickFiltersSource.INFRA_MONITORING,
|
||||
source: QuickFiltersSource.INFRA_MONITORING,
|
||||
},
|
||||
// `nin` is what the source asks for, but re-deriving the expression
|
||||
// normalises it. Nothing observes the difference: both infra pages send
|
||||
@@ -314,7 +313,7 @@ const TOGGLE_CASES: ToggleCase[] = [
|
||||
action: {
|
||||
value: 'b',
|
||||
checked: false,
|
||||
quickFilterSource: QuickFiltersSource.INFRA_MONITORING,
|
||||
source: QuickFiltersSource.INFRA_MONITORING,
|
||||
},
|
||||
expected: {
|
||||
items: [{ key: KEY, op: 'not in', value: ['a', 'b'] }],
|
||||
|
||||
@@ -46,18 +46,14 @@ export function removeManagedClauses(expression: string, key: string): string {
|
||||
}
|
||||
|
||||
// Sources that use backend APIs expecting short operator format (e.g., 'nin' instead of 'not in')
|
||||
const SOURCES_WITH_SHORT_OPERATORS: QuickFiltersSource[] = [
|
||||
QuickFiltersSource.INFRA_MONITORING,
|
||||
];
|
||||
const SOURCES_WITH_SHORT_OPERATORS = [QuickFiltersSource.INFRA_MONITORING];
|
||||
|
||||
/**
|
||||
* Returns the correct NOT_IN operator value based on source.
|
||||
* InfraMonitoring backend expects 'nin', others expect 'not in'.
|
||||
*/
|
||||
export function getNotInOperator(
|
||||
quickFilterSource: QuickFiltersSource,
|
||||
): string {
|
||||
if (SOURCES_WITH_SHORT_OPERATORS.includes(quickFilterSource)) {
|
||||
export function getNotInOperator(source: QuickFiltersSource): string {
|
||||
if (SOURCES_WITH_SHORT_OPERATORS.includes(source)) {
|
||||
return 'nin';
|
||||
}
|
||||
return getOperatorValue('NOT_IN');
|
||||
@@ -138,7 +134,7 @@ export function applyCheckboxToggle({
|
||||
currentQuery,
|
||||
activeQueryIndex,
|
||||
filter,
|
||||
quickFilterSource,
|
||||
source,
|
||||
attributeValues,
|
||||
value,
|
||||
checked,
|
||||
@@ -149,7 +145,7 @@ export function applyCheckboxToggle({
|
||||
currentQuery: Query;
|
||||
activeQueryIndex: number;
|
||||
filter: IQuickFiltersConfig;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source: QuickFiltersSource;
|
||||
attributeValues: string[];
|
||||
value: string;
|
||||
checked: boolean;
|
||||
@@ -244,7 +240,7 @@ export function applyCheckboxToggle({
|
||||
if (sectionType === SectionType.RELATED) {
|
||||
const newFilter: TagFilterItem = {
|
||||
id: uuid(),
|
||||
op: getNotInOperator(quickFilterSource),
|
||||
op: getNotInOperator(source),
|
||||
key: filter.attributeKey,
|
||||
value,
|
||||
};
|
||||
@@ -384,7 +380,7 @@ export function applyCheckboxToggle({
|
||||
if (!checked) {
|
||||
const newFilter = {
|
||||
...currentFilter,
|
||||
op: getNotInOperator(quickFilterSource),
|
||||
op: getNotInOperator(source),
|
||||
value: [currentFilter.value as string, value],
|
||||
};
|
||||
query.filters.items = query.filters.items.map((item) => {
|
||||
@@ -408,9 +404,7 @@ export function applyCheckboxToggle({
|
||||
// checked=true → user wants to select (IN), checked=false → exclude (NOT IN)
|
||||
const newFilterItem: TagFilterItem = {
|
||||
id: uuid(),
|
||||
op: checked
|
||||
? getOperatorValue(OPERATORS.IN)
|
||||
: getNotInOperator(quickFilterSource),
|
||||
op: checked ? getOperatorValue(OPERATORS.IN) : getNotInOperator(source),
|
||||
key: filter.attributeKey,
|
||||
value,
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import { SectionType } from './v2/itemRules';
|
||||
|
||||
interface UseCheckboxFilterActionsProps {
|
||||
filter: IQuickFiltersConfig;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source: QuickFiltersSource;
|
||||
attributeValues: string[];
|
||||
activeQueryIndex: number;
|
||||
onFilterChange?: ((query: Query) => void) | null;
|
||||
@@ -38,7 +38,7 @@ interface UseCheckboxFilterActionsReturn {
|
||||
*/
|
||||
function useCheckboxFilterActions({
|
||||
filter,
|
||||
quickFilterSource,
|
||||
source,
|
||||
attributeValues,
|
||||
activeQueryIndex,
|
||||
onFilterChange,
|
||||
@@ -65,7 +65,7 @@ function useCheckboxFilterActions({
|
||||
currentQuery,
|
||||
activeQueryIndex,
|
||||
filter,
|
||||
quickFilterSource,
|
||||
source,
|
||||
attributeValues,
|
||||
value,
|
||||
checked,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { render, RenderResult } from 'tests/test-utils';
|
||||
import { server, rest } from 'mocks-server/server';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
@@ -20,6 +21,7 @@ export const DEFAULT_FILTER: IQuickFiltersConfig = {
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
};
|
||||
|
||||
@@ -156,7 +158,7 @@ export function renderWithFilter(
|
||||
return render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
|
||||
@@ -33,7 +33,7 @@ import styles from './CheckboxFilterV2.module.scss';
|
||||
|
||||
interface CheckboxFilterV2Props {
|
||||
filter: IQuickFiltersConfig;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source: QuickFiltersSource;
|
||||
onFilterChange?: (query: Query) => void;
|
||||
onQuickFilterChange?: (data: QuickFilterChangeEventData) => void;
|
||||
useFieldApis: QuickFilterCheckboxUseFieldApis;
|
||||
@@ -42,18 +42,13 @@ interface CheckboxFilterV2Props {
|
||||
export default function CheckboxFilterV2(
|
||||
props: CheckboxFilterV2Props,
|
||||
): JSX.Element {
|
||||
const {
|
||||
quickFilterSource,
|
||||
filter,
|
||||
onFilterChange,
|
||||
onQuickFilterChange,
|
||||
useFieldApis,
|
||||
} = props;
|
||||
const { source, filter, onFilterChange, onQuickFilterChange, useFieldApis } =
|
||||
props;
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [userToggleState, setUserToggleState] = useState<boolean | null>(null);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const activeQueryIndex = useActiveQueryIndex(quickFilterSource);
|
||||
const activeQueryIndex = useActiveQueryIndex(source);
|
||||
|
||||
const {
|
||||
isOpen,
|
||||
@@ -80,7 +75,7 @@ export default function CheckboxFilterV2(
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace: useFieldApis.metricNamespace,
|
||||
quickFilterSource,
|
||||
source,
|
||||
startUnixMilli: useFieldApis.startUnixMilli,
|
||||
endUnixMilli: useFieldApis.endUnixMilli,
|
||||
enabled: isOpen,
|
||||
@@ -109,7 +104,7 @@ export default function CheckboxFilterV2(
|
||||
|
||||
const { onChange, onClear } = useCheckboxFilterActions({
|
||||
filter,
|
||||
quickFilterSource,
|
||||
source,
|
||||
attributeValues,
|
||||
activeQueryIndex,
|
||||
onFilterChange,
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('CheckboxFilterV2 - AI observability routing', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.AI_OBSERVABILITY}
|
||||
source={QuickFiltersSource.AI_OBSERVABILITY}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -44,7 +44,7 @@ describe('CheckboxFilterV2 - AI observability routing', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.AI_OBSERVABILITY}
|
||||
source={QuickFiltersSource.AI_OBSERVABILITY}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -59,8 +59,6 @@ describe('CheckboxFilterV2 - AI observability routing', () => {
|
||||
expect(params.get('endUnixMilli')).toBe(
|
||||
String(DEFAULT_USE_FIELD_APIS.endUnixMilli),
|
||||
);
|
||||
expect(params.get('signal')).toBeNull();
|
||||
expect(params.get('source')).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps non-AI sources on the signal-wide endpoint', async () => {
|
||||
@@ -72,7 +70,7 @@ describe('CheckboxFilterV2 - AI observability routing', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'custom.query = "value"',
|
||||
@@ -82,7 +82,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: null,
|
||||
@@ -116,7 +116,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={USE_FIELD_APIS_AUTO_DERIVE}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -154,7 +154,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={USE_FIELD_APIS_AUTO_DERIVE}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -185,7 +185,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={USE_FIELD_APIS_AUTO_DERIVE}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -222,7 +222,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={USE_FIELD_APIS_AUTO_DERIVE}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -264,7 +264,7 @@ describe('CheckboxFilterV2 - existingQuery calculation', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={USE_FIELD_APIS_AUTO_DERIVE}
|
||||
/>,
|
||||
undefined,
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { screen } from '@testing-library/react';
|
||||
import {
|
||||
TelemetrytypesSignalDTO,
|
||||
TelemetrytypesSourceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { render } from 'tests/test-utils';
|
||||
|
||||
import { QuickFiltersSource } from '../../../../types';
|
||||
|
||||
import CheckboxFilterV2 from '../CheckboxFilterV2';
|
||||
import {
|
||||
DEFAULT_FILTER,
|
||||
DEFAULT_USE_FIELD_APIS,
|
||||
setupServer,
|
||||
} from '../CheckboxFilterV2.testUtils';
|
||||
|
||||
setupServer();
|
||||
|
||||
describe('CheckboxFilterV2 - field APIs', () => {
|
||||
it('sends the signal and source of the quick filter source to the signal-wide endpoint', async () => {
|
||||
const requests: URLSearchParams[] = [];
|
||||
server.use(
|
||||
rest.get('http://localhost/api/v1/fields/values', (req, res, ctx) => {
|
||||
requests.push(req.url.searchParams);
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: { values: { stringValues: ['ingest'] } },
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.METER_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
|
||||
await expect(screen.findByText('ingest')).resolves.toBeInTheDocument();
|
||||
expect(requests[0].get('signal')).toBe(TelemetrytypesSignalDTO.metrics);
|
||||
expect(requests[0].get('source')).toBe(TelemetrytypesSourceDTO.meter);
|
||||
});
|
||||
});
|
||||
@@ -72,7 +72,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -101,7 +101,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -119,7 +119,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -173,7 +173,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -240,7 +240,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -288,7 +288,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -330,7 +330,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -394,7 +394,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -440,7 +440,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -468,7 +468,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={{ ...DEFAULT_FILTER, defaultOpen: false }}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -490,7 +490,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={{ ...DEFAULT_FILTER, defaultOpen: false }}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -516,7 +516,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={{ ...DEFAULT_FILTER, defaultOpen: false }}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -568,7 +568,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -601,7 +601,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -627,7 +627,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -652,7 +652,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -691,7 +691,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -714,7 +714,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
onFilterChange={onFilterChange}
|
||||
/>,
|
||||
@@ -761,7 +761,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
onFilterChange={onFilterChange}
|
||||
/>,
|
||||
@@ -837,7 +837,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
onFilterChange={onFilterChange}
|
||||
/>,
|
||||
@@ -971,7 +971,7 @@ describe('CheckboxFilterV2 - interactions', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={{ ...DEFAULT_FILTER, customRendererForValue: customRenderer }}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -55,7 +55,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -106,7 +106,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -140,7 +140,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -185,7 +185,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -221,7 +221,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -273,7 +273,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -321,7 +321,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
undefined,
|
||||
@@ -373,7 +373,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -424,7 +424,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -466,7 +466,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
@@ -521,7 +521,7 @@ describe('CheckboxFilterV2 - item rules', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={{
|
||||
...DEFAULT_USE_FIELD_APIS,
|
||||
existingQuery: 'service.name = "api"',
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -47,7 +47,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={closedFilter}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -104,7 +104,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -133,7 +133,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -152,7 +152,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -172,7 +172,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
@@ -195,7 +195,7 @@ describe('CheckboxFilterV2 - states', () => {
|
||||
render(
|
||||
<CheckboxFilterV2
|
||||
filter={DEFAULT_FILTER}
|
||||
quickFilterSource={QuickFiltersSource.TRACES_EXPLORER}
|
||||
source={QuickFiltersSource.TRACES_EXPLORER}
|
||||
useFieldApis={DEFAULT_USE_FIELD_APIS}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { useMemo } from 'react';
|
||||
import { TelemetrytypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { FieldValuesConfig } from 'api/querySuggestions/types';
|
||||
import { QUICK_FILTER_SOURCE_TO_SIGNAL } from 'components/QuickFilters/constants';
|
||||
import {
|
||||
IQuickFiltersConfig,
|
||||
QuickFiltersSource,
|
||||
} from 'components/QuickFilters/types';
|
||||
import { useFieldValuesSuggestion } from 'hooks/querySuggestions/useFieldValuesSuggestion';
|
||||
import { BuilderQueryType } from 'types/api/v5/queryRange';
|
||||
import { DATA_SOURCE_TO_SIGNAL } from 'types/common/queryBuilder';
|
||||
|
||||
interface UseFieldValuesProps {
|
||||
filter: IQuickFiltersConfig;
|
||||
searchText: string;
|
||||
existingQuery?: string;
|
||||
metricNamespace?: string;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source?: QuickFiltersSource;
|
||||
startUnixMilli?: number;
|
||||
endUnixMilli?: number;
|
||||
enabled: boolean;
|
||||
@@ -38,13 +38,12 @@ export function useFieldValues({
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace,
|
||||
quickFilterSource,
|
||||
source,
|
||||
startUnixMilli,
|
||||
endUnixMilli,
|
||||
enabled,
|
||||
}: UseFieldValuesProps): UseFieldValuesReturn {
|
||||
const isAIObservability =
|
||||
quickFilterSource === QuickFiltersSource.AI_OBSERVABILITY;
|
||||
const isAIObservability = source === QuickFiltersSource.AI_OBSERVABILITY;
|
||||
|
||||
const builderQueryType: BuilderQueryType | undefined = isAIObservability
|
||||
? 'builder_ai_query'
|
||||
@@ -60,12 +59,14 @@ export function useFieldValues({
|
||||
endUnixMilli,
|
||||
}
|
||||
: {
|
||||
signal: QUICK_FILTER_SOURCE_TO_SIGNAL[quickFilterSource],
|
||||
signal: filter.dataSource
|
||||
? DATA_SOURCE_TO_SIGNAL[filter.dataSource]
|
||||
: undefined,
|
||||
name: filter.attributeKey.key,
|
||||
searchText,
|
||||
existingQuery,
|
||||
metricNamespace,
|
||||
source: QUICK_FILTERS_SOURCE_TO_SOURCE[quickFilterSource],
|
||||
source: source ? QUICK_FILTERS_SOURCE_TO_SOURCE[source] : undefined,
|
||||
startUnixMilli,
|
||||
// This field does not affect the backend but I wanted to keep it here
|
||||
// in case we add the support in the future
|
||||
|
||||
@@ -31,11 +31,11 @@ export type { FilterType };
|
||||
function Duration({
|
||||
filter,
|
||||
onFilterChange,
|
||||
quickFilterSource,
|
||||
source,
|
||||
}: {
|
||||
filter: IQuickFiltersConfig;
|
||||
onFilterChange?: (query: Query) => void;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source: QuickFiltersSource;
|
||||
}): JSX.Element {
|
||||
const [selectedFilters, setSelectedFilters] =
|
||||
useState<
|
||||
@@ -52,7 +52,7 @@ function Duration({
|
||||
|
||||
const compositeQuery = useGetCompositeQueryParam();
|
||||
|
||||
const activeQueryIndex = useActiveQueryIndex(quickFilterSource);
|
||||
const activeQueryIndex = useActiveQueryIndex(source);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const syncSelectedFilters = useMemo((): FilterType => {
|
||||
|
||||
@@ -49,18 +49,15 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
className,
|
||||
config,
|
||||
handleFilterVisibilityChange,
|
||||
quickFilterSource,
|
||||
source,
|
||||
onFilterChange,
|
||||
onQuickFilterChange,
|
||||
signal,
|
||||
showFilterCollapse = true,
|
||||
showQueryName = true,
|
||||
useFieldApis,
|
||||
savedViewsHeader,
|
||||
} = props;
|
||||
const savedFiltersSource =
|
||||
quickFilterSource === QuickFiltersSource.INFRA_MONITORING
|
||||
? undefined
|
||||
: quickFilterSource;
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [params, setParams] = useApiMonitoringParams();
|
||||
const showIP = params.showIP ?? true;
|
||||
@@ -71,7 +68,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
customFilters,
|
||||
refetchCustomFilters,
|
||||
isCustomFiltersLoading,
|
||||
} = useFilterConfig({ savedFiltersSource, config });
|
||||
} = useFilterConfig({ signal, config });
|
||||
const {
|
||||
deniedPermissions: deniedSettingsPermissions,
|
||||
isLoading: isSettingsChecking,
|
||||
@@ -115,15 +112,15 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
|
||||
// Show dropdown in ListView only for TRACES_EXPLORER source
|
||||
const shouldShowDropdownInListView =
|
||||
isListView && quickFilterSource === QuickFiltersSource.TRACES_EXPLORER;
|
||||
isListView && source === QuickFiltersSource.TRACES_EXPLORER;
|
||||
|
||||
// AI observability builds a single query in the row-level views, so there is
|
||||
// no query for the selector to switch between.
|
||||
const isAIObservabilityRowView =
|
||||
quickFilterSource === QuickFiltersSource.AI_OBSERVABILITY &&
|
||||
source === QuickFiltersSource.AI_OBSERVABILITY &&
|
||||
(isListView || panelType === PANEL_TYPES.TRACE);
|
||||
|
||||
const activeQueryIndex = useActiveQueryIndex(quickFilterSource);
|
||||
const activeQueryIndex = useActiveQueryIndex(source);
|
||||
|
||||
// clear all the filters for the query which is in sync with filters
|
||||
const handleReset = (): void => {
|
||||
@@ -293,7 +290,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
|
||||
const renderContent = (): JSX.Element => (
|
||||
<>
|
||||
{quickFilterSource === QuickFiltersSource.API_MONITORING && (
|
||||
{source === QuickFiltersSource.API_MONITORING && (
|
||||
<div className="api-quick-filters-header">
|
||||
<Typography.Text>Show IP addresses</Typography.Text>
|
||||
<Switch
|
||||
@@ -316,7 +313,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
return (
|
||||
<Duration
|
||||
key={filter.attributeKey.key}
|
||||
quickFilterSource={quickFilterSource}
|
||||
source={source}
|
||||
filter={filter}
|
||||
onFilterChange={onFilterChange}
|
||||
/>
|
||||
@@ -327,7 +324,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
return (
|
||||
<CheckboxV2
|
||||
key={filter.attributeKey.key}
|
||||
quickFilterSource={quickFilterSource}
|
||||
source={source}
|
||||
filter={filter}
|
||||
onFilterChange={onFilterChange}
|
||||
onQuickFilterChange={onQuickFilterChange}
|
||||
@@ -366,14 +363,14 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
<div className="quick-filters-container">
|
||||
<div className="quick-filters">
|
||||
{savedViewsHeader}
|
||||
{quickFilterSource !== QuickFiltersSource.INFRA_MONITORING && (
|
||||
{source !== QuickFiltersSource.INFRA_MONITORING && (
|
||||
<section className="header">
|
||||
{renderLeftActions()}
|
||||
{renderRightActions()}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{savedFiltersSource ? (
|
||||
{signal ? (
|
||||
<AuthZGuardContent
|
||||
checks={[QuickFilterReadPermission]}
|
||||
fallbackOnLoading={filtersSkeleton}
|
||||
@@ -394,9 +391,9 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{isSettingsOpen && savedFiltersSource && (
|
||||
{isSettingsOpen && (
|
||||
<QuickFiltersSettings
|
||||
savedFiltersSource={savedFiltersSource}
|
||||
signal={signal}
|
||||
setIsSettingsOpen={setIsSettingsOpen}
|
||||
customFilters={customFilters}
|
||||
refetchCustomFilters={refetchCustomFilters}
|
||||
@@ -410,6 +407,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
|
||||
QuickFilters.defaultProps = {
|
||||
onFilterChange: null,
|
||||
signal: '',
|
||||
config: [],
|
||||
showFilterCollapse: true,
|
||||
showQueryName: true,
|
||||
|
||||
@@ -5,7 +5,6 @@ import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import SavedViewsHeader from 'container/SavedViews/SavedViewsHeader';
|
||||
import SavedViewsPanel from 'container/SavedViews/SavedViewsPanel';
|
||||
import SavedViewsRestore from 'container/SavedViews/SavedViewsRestore';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
|
||||
import QuickFilters from '../QuickFilters';
|
||||
import { SIDEBAR_TOOLTIP_DELAY_MS } from './constants';
|
||||
@@ -50,10 +49,9 @@ function QuickFiltersLayout({
|
||||
children,
|
||||
}: QuickFiltersLayoutProps): JSX.Element {
|
||||
const [isViewsListOpen, setIsViewsListOpen] = useState(false);
|
||||
const isSavedViewEnabled = useSavedViewEnabled();
|
||||
|
||||
const hasQuickFilters = !!quickFilterProps;
|
||||
const hasSavedViews = !!savedViewProps && isSavedViewEnabled;
|
||||
const hasSavedViews = !!savedViewProps;
|
||||
|
||||
const showSidebar = showFilters && (hasQuickFilters || hasSavedViews);
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { QuickFiltersSource } from '../../types';
|
||||
@@ -20,10 +19,6 @@ jest.mock('../QuickFiltersLayout.module.scss', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSavedViewEnabled', () => ({
|
||||
useSavedViewEnabled: jest.fn(() => true),
|
||||
}));
|
||||
|
||||
jest.mock('container/SavedViews/SavedViewsRestore', () => ({
|
||||
__esModule: true,
|
||||
default: ({ source }: { source: string }): JSX.Element => (
|
||||
@@ -34,17 +29,17 @@ jest.mock('container/SavedViews/SavedViewsRestore', () => ({
|
||||
jest.mock('../../QuickFilters', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
quickFilterSource,
|
||||
source,
|
||||
savedViewsHeader,
|
||||
handleFilterVisibilityChange,
|
||||
}: {
|
||||
quickFilterSource: string;
|
||||
source: string;
|
||||
savedViewsHeader?: React.ReactNode;
|
||||
handleFilterVisibilityChange?: () => void;
|
||||
}): JSX.Element => (
|
||||
<div data-testid="quick-filters">
|
||||
{savedViewsHeader}
|
||||
{quickFilterSource}
|
||||
{source}
|
||||
{handleFilterVisibilityChange && (
|
||||
<Button
|
||||
size="md"
|
||||
@@ -62,7 +57,7 @@ jest.mock('../../QuickFilters', () => ({
|
||||
}));
|
||||
|
||||
const quickFilterProps = {
|
||||
quickFilterSource: QuickFiltersSource.TRACES_EXPLORER,
|
||||
source: QuickFiltersSource.TRACES_EXPLORER,
|
||||
useFieldApis: { startUnixMilli: 0, endUnixMilli: 0 },
|
||||
};
|
||||
const savedViewProps = { source: SavedviewtypesSourceDTO.traces };
|
||||
@@ -71,10 +66,6 @@ const drawer = (): HTMLElement =>
|
||||
screen.getByTestId('quick-filters-layout-drawer');
|
||||
|
||||
describe('QuickFiltersLayout', () => {
|
||||
beforeEach(() => {
|
||||
jest.mocked(useSavedViewEnabled).mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('renders QuickFilters with the given props inside the filters pane', () => {
|
||||
render(
|
||||
<QuickFiltersLayout showFilters quickFilterProps={quickFilterProps}>
|
||||
@@ -149,35 +140,6 @@ describe('QuickFiltersLayout', () => {
|
||||
});
|
||||
|
||||
describe('saved views', () => {
|
||||
it('ignores savedViewProps while the flag is off', () => {
|
||||
jest.mocked(useSavedViewEnabled).mockReturnValue(false);
|
||||
const { unmount } = render(
|
||||
<QuickFiltersLayout
|
||||
showFilters
|
||||
quickFilterProps={quickFilterProps}
|
||||
savedViewProps={savedViewProps}
|
||||
>
|
||||
<div>content</div>
|
||||
</QuickFiltersLayout>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('quick-filters')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('saved-views-header')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('saved-views-panel')).not.toBeInTheDocument();
|
||||
|
||||
// Saved views only, so nothing is left to show.
|
||||
unmount();
|
||||
render(
|
||||
<QuickFiltersLayout showFilters savedViewProps={savedViewProps}>
|
||||
<div>content</div>
|
||||
</QuickFiltersLayout>,
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId('quick-filters-layout-filters'),
|
||||
).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('saved-views-restore')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens the panel from the header and slides the quick filters drawer, keeping the same QuickFilters node', async () => {
|
||||
render(
|
||||
<QuickFiltersLayout
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Button, Skeleton } from 'antd';
|
||||
import {
|
||||
QuickfiltertypesSourceDTO,
|
||||
TelemetrytypesSourceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { TelemetrytypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { FieldKeysConfig } from 'api/querySuggestions/types';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { QUICK_FILTER_SOURCE_TO_SIGNAL } from 'components/QuickFilters/constants';
|
||||
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import { useFieldKeysSuggestion } from 'hooks/querySuggestions/useFieldKeysSuggestion';
|
||||
import {
|
||||
@@ -15,6 +13,7 @@ import {
|
||||
FieldDataType,
|
||||
TelemetryFieldKey,
|
||||
} from 'types/api/v5/queryRange';
|
||||
import { DATA_SOURCE_TO_SIGNAL } from 'types/common/queryBuilder';
|
||||
|
||||
function OtherFiltersSkeleton(): JSX.Element {
|
||||
return (
|
||||
@@ -33,20 +32,18 @@ function OtherFiltersSkeleton(): JSX.Element {
|
||||
}
|
||||
|
||||
function OtherFilters({
|
||||
savedFiltersSource,
|
||||
signal,
|
||||
inputValue,
|
||||
addedFilters,
|
||||
setAddedFilters,
|
||||
}: {
|
||||
savedFiltersSource: QuickfiltertypesSourceDTO;
|
||||
signal: SignalType | undefined;
|
||||
inputValue: string;
|
||||
addedFilters: TelemetryFieldKey[];
|
||||
setAddedFilters: React.Dispatch<React.SetStateAction<TelemetryFieldKey[]>>;
|
||||
}): JSX.Element {
|
||||
const isMeterDataSource =
|
||||
savedFiltersSource === QuickfiltertypesSourceDTO.meter;
|
||||
const isAIObservability =
|
||||
savedFiltersSource === QuickfiltertypesSourceDTO.ai_observability;
|
||||
const isMeterDataSource = signal === SignalType.METER_EXPLORER;
|
||||
const isAIObservability = signal === SignalType.AI_OBSERVABILITY;
|
||||
|
||||
const builderQueryType: BuilderQueryType | undefined = isAIObservability
|
||||
? 'builder_ai_query'
|
||||
@@ -56,7 +53,9 @@ function OtherFilters({
|
||||
? { searchText: inputValue }
|
||||
: {
|
||||
searchText: inputValue,
|
||||
signal: QUICK_FILTER_SOURCE_TO_SIGNAL[savedFiltersSource],
|
||||
signal: signal
|
||||
? DATA_SOURCE_TO_SIGNAL[SIGNAL_DATA_SOURCE_MAP[signal]]
|
||||
: undefined,
|
||||
source: isMeterDataSource ? TelemetrytypesSourceDTO.meter : undefined,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Button as SignozButton } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { Button } from 'antd';
|
||||
import { Check, TableColumnsSplit, X } from '@signozhq/icons';
|
||||
import { QuickfiltertypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { SignalType } from '../types';
|
||||
import AddedFilters from './AddedFilters';
|
||||
import useQuickFilterSettings from './hooks/useQuickFilterSettings';
|
||||
import OtherFilters from './OtherFilters';
|
||||
@@ -12,12 +12,12 @@ import OtherFilters from './OtherFilters';
|
||||
import './QuickFiltersSettings.styles.scss';
|
||||
|
||||
function QuickFiltersSettings({
|
||||
savedFiltersSource,
|
||||
signal,
|
||||
setIsSettingsOpen,
|
||||
customFilters,
|
||||
refetchCustomFilters,
|
||||
}: {
|
||||
savedFiltersSource: QuickfiltertypesSourceDTO;
|
||||
signal: SignalType | undefined;
|
||||
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
||||
customFilters: TelemetryFieldKey[];
|
||||
refetchCustomFilters: () => void;
|
||||
@@ -37,7 +37,7 @@ function QuickFiltersSettings({
|
||||
setIsSettingsOpen,
|
||||
customFilters,
|
||||
refetchCustomFilters,
|
||||
savedFiltersSource,
|
||||
signal,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -73,7 +73,7 @@ function QuickFiltersSettings({
|
||||
setAddedFilters={setAddedFilters}
|
||||
/>
|
||||
<OtherFilters
|
||||
savedFiltersSource={savedFiltersSource}
|
||||
signal={signal}
|
||||
inputValue={debouncedInputValue}
|
||||
addedFilters={addedFilters}
|
||||
setAddedFilters={setAddedFilters}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { ENVIRONMENT } from 'constants/env';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render } from 'tests/test-utils';
|
||||
import { QuickfiltertypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { SignalType } from '../../types';
|
||||
import OtherFilters from '../OtherFilters';
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
@@ -43,12 +43,10 @@ describe('OtherFilters - AI observability keys', () => {
|
||||
);
|
||||
});
|
||||
|
||||
function renderOtherFilters(
|
||||
savedFiltersSource: QuickfiltertypesSourceDTO,
|
||||
): void {
|
||||
function renderOtherFilters(signal: SignalType): void {
|
||||
render(
|
||||
<OtherFilters
|
||||
savedFiltersSource={savedFiltersSource}
|
||||
signal={signal}
|
||||
inputValue=""
|
||||
addedFilters={[]}
|
||||
setAddedFilters={jest.fn()}
|
||||
@@ -57,7 +55,7 @@ describe('OtherFilters - AI observability keys', () => {
|
||||
}
|
||||
|
||||
it('reads AI observability keys from their own endpoint', async () => {
|
||||
renderOtherFilters(QuickfiltertypesSourceDTO.ai_observability);
|
||||
renderOtherFilters(SignalType.AI_OBSERVABILITY);
|
||||
|
||||
await expect(
|
||||
screen.findByText('gen_ai.request.model'),
|
||||
@@ -66,7 +64,7 @@ describe('OtherFilters - AI observability keys', () => {
|
||||
});
|
||||
|
||||
it('does not narrow the AI keys by fieldContext', async () => {
|
||||
renderOtherFilters(QuickfiltertypesSourceDTO.ai_observability);
|
||||
renderOtherFilters(SignalType.AI_OBSERVABILITY);
|
||||
|
||||
// A `trace` context would return only the computed per-trace aggregates,
|
||||
// which cannot be filtered on.
|
||||
@@ -75,7 +73,7 @@ describe('OtherFilters - AI observability keys', () => {
|
||||
});
|
||||
|
||||
it('keeps other signals on the signal-wide keys endpoint', async () => {
|
||||
renderOtherFilters(QuickfiltertypesSourceDTO.traces);
|
||||
renderOtherFilters(SignalType.TRACES);
|
||||
|
||||
await expect(screen.findByText('http.route')).resolves.toBeInTheDocument();
|
||||
await waitFor(() => expect(aiKeysParams).toBeUndefined());
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const SIGNAL_DATA_SOURCE_MAP = {
|
||||
[SignalType.LOGS]: DataSource.LOGS,
|
||||
[SignalType.TRACES]: DataSource.TRACES,
|
||||
[SignalType.EXCEPTIONS]: DataSource.TRACES,
|
||||
[SignalType.API_MONITORING]: DataSource.TRACES,
|
||||
[SignalType.METER_EXPLORER]: DataSource.METRICS,
|
||||
[SignalType.AI_OBSERVABILITY]: DataSource.TRACES,
|
||||
};
|
||||
@@ -4,8 +4,8 @@ import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
TelemetrytypesFieldContextDTO,
|
||||
TelemetrytypesFieldDataTypeDTO,
|
||||
QuickfiltertypesSourceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
@@ -16,7 +16,7 @@ interface UseQuickFilterSettingsProps {
|
||||
setIsSettingsOpen: (isSettingsOpen: boolean) => void;
|
||||
customFilters: TelemetryFieldKey[];
|
||||
refetchCustomFilters: () => void;
|
||||
savedFiltersSource: QuickfiltertypesSourceDTO;
|
||||
signal?: SignalType;
|
||||
}
|
||||
|
||||
interface UseQuickFilterSettingsReturn {
|
||||
@@ -37,7 +37,7 @@ const useQuickFilterSettings = ({
|
||||
customFilters,
|
||||
setIsSettingsOpen,
|
||||
refetchCustomFilters,
|
||||
savedFiltersSource,
|
||||
signal,
|
||||
}: UseQuickFilterSettingsProps): UseQuickFilterSettingsReturn => {
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [debouncedInputValue, setDebouncedInputValue] = useState<string>('');
|
||||
@@ -113,19 +113,21 @@ const useQuickFilterSettings = ({
|
||||
);
|
||||
|
||||
const handleSaveChanges = useCallback((): void => {
|
||||
updateCustomFilters({
|
||||
pathParams: { source: savedFiltersSource },
|
||||
data: {
|
||||
// Send only the stored TelemetryFieldKey fields; the composite `key`
|
||||
// is UI-only.
|
||||
filters: addedFilters.map((filter) => ({
|
||||
name: filter.name,
|
||||
fieldContext: filter.fieldContext as TelemetrytypesFieldContextDTO,
|
||||
fieldDataType: filter.fieldDataType as TelemetrytypesFieldDataTypeDTO,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}, [addedFilters, savedFiltersSource, updateCustomFilters]);
|
||||
if (signal) {
|
||||
updateCustomFilters({
|
||||
pathParams: { source: signal },
|
||||
data: {
|
||||
// Send only the stored TelemetryFieldKey fields; the composite `key`
|
||||
// is UI-only.
|
||||
filters: addedFilters.map((filter) => ({
|
||||
name: filter.name,
|
||||
fieldContext: filter.fieldContext as TelemetrytypesFieldContextDTO,
|
||||
fieldDataType: filter.fieldDataType as TelemetrytypesFieldDataTypeDTO,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [addedFilters, signal, updateCustomFilters]);
|
||||
|
||||
return {
|
||||
handleSettingsClose,
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
import { QuickFiltersSource } from './types';
|
||||
|
||||
export const QUICK_FILTER_SOURCE_TO_SIGNAL: Record<
|
||||
QuickFiltersSource,
|
||||
TelemetrytypesSignalDTO
|
||||
> = {
|
||||
[QuickFiltersSource.LOGS_EXPLORER]: TelemetrytypesSignalDTO.logs,
|
||||
[QuickFiltersSource.TRACES_EXPLORER]: TelemetrytypesSignalDTO.traces,
|
||||
[QuickFiltersSource.EXCEPTIONS]: TelemetrytypesSignalDTO.traces,
|
||||
[QuickFiltersSource.API_MONITORING]: TelemetrytypesSignalDTO.traces,
|
||||
[QuickFiltersSource.AI_OBSERVABILITY]: TelemetrytypesSignalDTO.traces,
|
||||
[QuickFiltersSource.METER_EXPLORER]: TelemetrytypesSignalDTO.metrics,
|
||||
[QuickFiltersSource.INFRA_MONITORING]: TelemetrytypesSignalDTO.metrics,
|
||||
};
|
||||
@@ -10,26 +10,26 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
* In ListView most sources use index 0; TRACES_EXPLORER and every non-ListView
|
||||
* mode track the last focused query.
|
||||
*/
|
||||
function useActiveQueryIndex(quickFilterSource: QuickFiltersSource): number {
|
||||
function useActiveQueryIndex(source: QuickFiltersSource): number {
|
||||
const { lastUsedQuery, panelType } = useQueryBuilder();
|
||||
const isListView = panelType === PANEL_TYPES.LIST;
|
||||
|
||||
return useMemo(() => {
|
||||
// AI observability builds a single query in the row-level views, so its
|
||||
// filters always drive the first one there.
|
||||
if (quickFilterSource === QuickFiltersSource.AI_OBSERVABILITY) {
|
||||
if (source === QuickFiltersSource.AI_OBSERVABILITY) {
|
||||
return isListView || panelType === PANEL_TYPES.TRACE
|
||||
? 0
|
||||
: lastUsedQuery || 0;
|
||||
}
|
||||
|
||||
if (isListView) {
|
||||
return quickFilterSource === QuickFiltersSource.TRACES_EXPLORER
|
||||
return source === QuickFiltersSource.TRACES_EXPLORER
|
||||
? lastUsedQuery || 0
|
||||
: 0;
|
||||
}
|
||||
return lastUsedQuery || 0;
|
||||
}, [isListView, panelType, quickFilterSource, lastUsedQuery]);
|
||||
}, [isListView, panelType, source, lastUsedQuery]);
|
||||
}
|
||||
|
||||
export default useActiveQueryIndex;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useGetQuickFilters } from 'api/generated/services/quick-filter';
|
||||
import { QuickfiltertypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { IQuickFiltersConfig } from '../types';
|
||||
import { IQuickFiltersConfig, SignalType } from '../types';
|
||||
import { getFilterConfig } from '../utils';
|
||||
|
||||
interface UseFilterConfigProps {
|
||||
savedFiltersSource?: QuickfiltertypesSourceDTO;
|
||||
signal?: SignalType;
|
||||
config: IQuickFiltersConfig[];
|
||||
}
|
||||
interface UseFilterConfigReturn {
|
||||
@@ -19,7 +18,7 @@ interface UseFilterConfigReturn {
|
||||
}
|
||||
|
||||
const useFilterConfig = ({
|
||||
savedFiltersSource,
|
||||
signal,
|
||||
config,
|
||||
}: UseFilterConfigProps): UseFilterConfigReturn => {
|
||||
const {
|
||||
@@ -27,8 +26,8 @@ const useFilterConfig = ({
|
||||
data,
|
||||
refetch,
|
||||
} = useGetQuickFilters(
|
||||
{ source: savedFiltersSource ?? '' },
|
||||
{ query: { enabled: !!savedFiltersSource } },
|
||||
{ source: signal ?? '' },
|
||||
{ query: { enabled: !!signal } },
|
||||
);
|
||||
|
||||
const customFilters = useMemo<TelemetryFieldKey[]>(
|
||||
@@ -36,11 +35,11 @@ const useFilterConfig = ({
|
||||
[data],
|
||||
);
|
||||
|
||||
const isDynamicFilters = !!savedFiltersSource;
|
||||
const isDynamicFilters = !!signal;
|
||||
|
||||
const filterConfig = useMemo(
|
||||
() => getFilterConfig(customFilters, config),
|
||||
[config, customFilters],
|
||||
() => getFilterConfig(signal, customFilters, config),
|
||||
[config, customFilters, signal],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -89,15 +89,6 @@ export const handlers = [
|
||||
fieldValuesHandler(['checkout', 'frontend', 'payments']),
|
||||
];
|
||||
|
||||
export const noSavedFiltersHandler = rest.get(
|
||||
'http://localhost/api/v2/quick_filters/logs',
|
||||
(_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json(quickFiltersResponse(QuickfiltertypesSourceDTO.logs, [])),
|
||||
),
|
||||
);
|
||||
|
||||
export const loadingFiltersHandlers = [
|
||||
rest.get('http://localhost/api/v2/quick_filters/logs', (_req, res, ctx) =>
|
||||
res(ctx.delay('infinite')),
|
||||
|
||||
@@ -15,9 +15,8 @@ import {
|
||||
loadingFiltersHandlers,
|
||||
queryBuilder,
|
||||
selectedServiceQueryBuilder,
|
||||
noSavedFiltersHandler,
|
||||
} from './QuickFilters.stories.mocks';
|
||||
import { QuickFiltersSource } from '../types';
|
||||
import { QuickFiltersSource, SignalType } from '../types';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Quick Filters',
|
||||
@@ -34,7 +33,8 @@ const meta = {
|
||||
args: {
|
||||
config: checkboxConfig,
|
||||
handleFilterVisibilityChange: (): void => undefined,
|
||||
quickFilterSource: QuickFiltersSource.LOGS_EXPLORER,
|
||||
signal: SignalType.LOGS,
|
||||
source: QuickFiltersSource.LOGS_EXPLORER,
|
||||
useFieldApis: fieldApis,
|
||||
},
|
||||
parameters: {
|
||||
@@ -94,16 +94,13 @@ export const LoadingFilters: Story = {
|
||||
|
||||
/** Empty: a loaded quick-filter configuration with no filters has no result rows. */
|
||||
export const NoResults: Story = {
|
||||
args: { config: [] },
|
||||
parameters: {
|
||||
msw: { handlers: [noSavedFiltersHandler, ...handlers] },
|
||||
},
|
||||
args: { config: [], signal: undefined },
|
||||
};
|
||||
|
||||
/** Selection: an expanded checkbox shows the actual selected service values. */
|
||||
export const SelectedExpandedCheckbox: Story = {
|
||||
args: { signal: undefined },
|
||||
parameters: {
|
||||
msw: { handlers: [noSavedFiltersHandler, ...handlers] },
|
||||
signoz: {
|
||||
queryBuilder: selectedServiceQueryBuilder,
|
||||
},
|
||||
@@ -114,18 +111,12 @@ export const SelectedExpandedCheckbox: Story = {
|
||||
* Every tooltip the panel renders, held open: the reveal on each truncated
|
||||
* filter value. Nothing bounds those values, so the panel is answered with
|
||||
* service names long enough to be cut. The Service name filter carries them, so
|
||||
* the workspace's own saved filter list is answered empty.
|
||||
* the signal that would add the workspace's own dynamic filters is left off.
|
||||
*/
|
||||
export const Tooltips: TooltipsStory = {
|
||||
args: { tooltipsOpen: true },
|
||||
args: { signal: undefined, tooltipsOpen: true },
|
||||
parameters: {
|
||||
msw: {
|
||||
handlers: [
|
||||
noSavedFiltersHandler,
|
||||
fieldValuesHandler(LONG_FILTER_VALUES),
|
||||
...handlers,
|
||||
],
|
||||
},
|
||||
msw: { handlers: [fieldValuesHandler(LONG_FILTER_VALUES), ...handlers] },
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { rest } from 'msw';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import QuickFilters from '../QuickFilters';
|
||||
import { QuickFiltersSource } from '../types';
|
||||
import { QuickFiltersSource, SignalType } from '../types';
|
||||
import { QuickFiltersConfig, QuickFiltersFieldApis } from './constants';
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
@@ -35,7 +35,8 @@ jest.mock('container/ApiMonitoring/queryParams');
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/logs`;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/${SIGNAL}`;
|
||||
const fieldsKeysURL = `${BASE_URL}/api/v1/fields/keys`;
|
||||
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||
|
||||
@@ -80,10 +81,11 @@ afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function renderWithSavedList(): void {
|
||||
function renderWithSignal(): void {
|
||||
render(
|
||||
<QuickFilters
|
||||
quickFilterSource={QuickFiltersSource.LOGS_EXPLORER}
|
||||
source={QuickFiltersSource.LOGS_EXPLORER}
|
||||
signal={SIGNAL}
|
||||
handleFilterVisibilityChange={jest.fn()}
|
||||
useFieldApis={QuickFiltersFieldApis}
|
||||
/>,
|
||||
@@ -93,7 +95,7 @@ function renderWithSavedList(): void {
|
||||
function renderStaticConfig(): void {
|
||||
render(
|
||||
<QuickFilters
|
||||
quickFilterSource={QuickFiltersSource.INFRA_MONITORING}
|
||||
source={QuickFiltersSource.EXCEPTIONS}
|
||||
config={QuickFiltersConfig}
|
||||
handleFilterVisibilityChange={jest.fn()}
|
||||
useFieldApis={QuickFiltersFieldApis}
|
||||
@@ -106,7 +108,7 @@ describe('QuickFilters - AuthZ', () => {
|
||||
it('shows the inline denial instead of the filters, header stays', async () => {
|
||||
server.use(setupAuthzDeny(QuickFilterReadPermission));
|
||||
|
||||
renderWithSavedList();
|
||||
renderWithSignal();
|
||||
|
||||
await expect(
|
||||
screen.findByText(NOT_AUTHORIZED_TEXT),
|
||||
@@ -131,7 +133,7 @@ describe('QuickFilters - AuthZ', () => {
|
||||
it('renders the filters but disables the settings trigger', async () => {
|
||||
server.use(setupAuthzDeny(QuickFilterUpdatePermission));
|
||||
|
||||
renderWithSavedList();
|
||||
renderWithSignal();
|
||||
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
@@ -156,7 +158,7 @@ describe('QuickFilters - AuthZ', () => {
|
||||
it('renders the filters and opens settings from the trigger', async () => {
|
||||
server.use(setupAuthzAdmin());
|
||||
|
||||
renderWithSavedList();
|
||||
renderWithSignal();
|
||||
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
@@ -174,7 +176,7 @@ describe('QuickFilters - AuthZ', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('static config pages (no saved list)', () => {
|
||||
describe('static config pages (no signal)', () => {
|
||||
it('is not gated even when every permission is denied', async () => {
|
||||
server.use(setupAuthzDenyAll());
|
||||
|
||||
@@ -193,7 +195,7 @@ describe('QuickFilters - AuthZ', () => {
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => res(ctx.delay('infinite'))),
|
||||
);
|
||||
|
||||
renderWithSavedList();
|
||||
renderWithSignal();
|
||||
|
||||
await waitFor(() =>
|
||||
// eslint-disable-next-line testing-library/no-node-access
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { TelemetrytypesSignalDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import {
|
||||
ApiMonitoringParams,
|
||||
useApiMonitoringParams,
|
||||
} from 'container/ApiMonitoring/queryParams';
|
||||
import { getHostsQuickFiltersConfig } from 'container/InfraMonitoringHostsV2/utils';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import {
|
||||
otherFiltersResponse,
|
||||
@@ -19,7 +16,7 @@ import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import QuickFilters from '../QuickFilters';
|
||||
import { IQuickFiltersConfig, QuickFiltersSource } from '../types';
|
||||
import { IQuickFiltersConfig, QuickFiltersSource, SignalType } from '../types';
|
||||
import { QuickFiltersConfig, QuickFiltersFieldApis } from './constants';
|
||||
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
@@ -36,9 +33,9 @@ const mockSetApiMonitoringParams = jest.fn() as jest.MockedFunction<
|
||||
const mockUseApiMonitoringParams = jest.mocked(useApiMonitoringParams);
|
||||
|
||||
const BASE_URL = ENVIRONMENT.baseURL;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/logs`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v2/quick_filters/logs`;
|
||||
const anyQuickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/:source`;
|
||||
const SIGNAL = SignalType.LOGS;
|
||||
const quickFiltersListURL = `${BASE_URL}/api/v2/quick_filters/${SIGNAL}`;
|
||||
const saveQuickFiltersURL = `${BASE_URL}/api/v2/quick_filters/${SIGNAL}`;
|
||||
const quickFiltersSuggestionsURL = `${BASE_URL}/api/v1/fields/keys`;
|
||||
const fieldsValuesURL = `${BASE_URL}/api/v1/fields/values`;
|
||||
|
||||
@@ -57,9 +54,6 @@ const setupServer = (): void => {
|
||||
rest.get(quickFiltersListURL, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(quickFiltersListResponse)),
|
||||
),
|
||||
rest.get(anyQuickFiltersListURL, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ status: 'success', data: { filters: [] } })),
|
||||
),
|
||||
rest.get(quickFiltersSuggestionsURL, (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json(otherFiltersResponse)),
|
||||
),
|
||||
@@ -74,24 +68,48 @@ const setupServer = (): void => {
|
||||
};
|
||||
|
||||
function TestQuickFilters({
|
||||
quickFilterSource = QuickFiltersSource.EXCEPTIONS,
|
||||
signal = SignalType.LOGS,
|
||||
config = QuickFiltersConfig,
|
||||
}: {
|
||||
quickFilterSource?: QuickFiltersSource;
|
||||
signal?: SignalType;
|
||||
config?: IQuickFiltersConfig[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<QuickFilters
|
||||
quickFilterSource={quickFilterSource}
|
||||
source={QuickFiltersSource.EXCEPTIONS}
|
||||
config={config}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={QuickFiltersFieldApis}
|
||||
signal={signal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TestQuickFilters.defaultProps = {
|
||||
quickFilterSource: QuickFiltersSource.EXCEPTIONS,
|
||||
signal: '',
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
function TestQuickFiltersApiMonitoring({
|
||||
signal = SignalType.LOGS,
|
||||
config = QuickFiltersConfig,
|
||||
}: {
|
||||
signal?: SignalType;
|
||||
config?: IQuickFiltersConfig[];
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<QuickFilters
|
||||
source={QuickFiltersSource.API_MONITORING}
|
||||
config={config}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={QuickFiltersFieldApis}
|
||||
signal={signal}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TestQuickFiltersApiMonitoring.defaultProps = {
|
||||
signal: '',
|
||||
config: QuickFiltersConfig,
|
||||
};
|
||||
|
||||
@@ -269,14 +287,10 @@ describe('Quick Filters', () => {
|
||||
it('toggles Show IP addresses and updates API Monitoring params', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.API_MONITORING} />,
|
||||
);
|
||||
render(<TestQuickFiltersApiMonitoring />);
|
||||
|
||||
// Switch should be rendered and initially checked
|
||||
await expect(
|
||||
screen.findByText('Show IP addresses'),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(screen.getByText('Show IP addresses')).toBeInTheDocument();
|
||||
const toggle = screen.getByRole('switch');
|
||||
expect(toggle).toHaveAttribute('aria-checked', 'true');
|
||||
|
||||
@@ -294,9 +308,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
it('loads the custom filters correctly', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
|
||||
expect(screen.getByText('Filters for')).toBeInTheDocument();
|
||||
expect(screen.getByText(QUERY_NAME)).toBeInTheDocument();
|
||||
@@ -356,9 +368,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
),
|
||||
);
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
@@ -386,9 +396,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
it('adds a filter from OTHER FILTERS to ADDED FILTERS when clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
@@ -409,9 +417,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
it('removes a filter from ADDED FILTERS and moves it to OTHER FILTERS', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
@@ -440,9 +446,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
it('restores original filter state on Discard', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
@@ -484,9 +488,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
it('saves the updated filters by calling PUT with correct payload', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
@@ -523,9 +525,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
pointerEventsCheck: 0,
|
||||
});
|
||||
|
||||
const { getByTestId } = render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
const { getByTestId } = render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
expect(screen.getByText('Duration')).toBeInTheDocument();
|
||||
|
||||
@@ -579,7 +579,7 @@ describe('Quick Filters with custom filters', () => {
|
||||
});
|
||||
|
||||
describe('Quick Filters refetch behavior', () => {
|
||||
it('fetches custom filters on every mount for a page with a saved list', async () => {
|
||||
it('fetches custom filters on every mount when signal is provided', async () => {
|
||||
let getCalls = 0;
|
||||
|
||||
server.use(
|
||||
@@ -589,18 +589,14 @@ describe('Quick Filters refetch behavior', () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const { unmount } = render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
const { unmount } = render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
unmount();
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
@@ -608,24 +604,19 @@ describe('Quick Filters refetch behavior', () => {
|
||||
expect(getCalls).toBe(2);
|
||||
});
|
||||
|
||||
it('does not fetch custom filters for a page without a saved list', async () => {
|
||||
it('does not fetch custom filters when signal is undefined', async () => {
|
||||
let getCalls = 0;
|
||||
|
||||
server.use(
|
||||
rest.get(anyQuickFiltersListURL, (_req, res, ctx) => {
|
||||
rest.get(quickFiltersListURL, (_req, res, ctx) => {
|
||||
getCalls += 1;
|
||||
return res(ctx.status(200), ctx.json(quickFiltersListResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.INFRA_MONITORING} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={undefined} />);
|
||||
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
).resolves.toBeInTheDocument();
|
||||
expect(getCalls).toBe(0);
|
||||
await waitFor(() => expect(getCalls).toBe(0));
|
||||
});
|
||||
|
||||
it('refetches custom filters after saving settings', async () => {
|
||||
@@ -644,9 +635,7 @@ describe('Quick Filters refetch behavior', () => {
|
||||
);
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
@@ -698,9 +687,7 @@ describe('Quick Filters refetch behavior', () => {
|
||||
);
|
||||
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(
|
||||
<TestQuickFilters quickFilterSource={QuickFiltersSource.LOGS_EXPLORER} />,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
|
||||
await expect(
|
||||
screen.findByText(FILTER_SERVICE_NAME),
|
||||
@@ -731,47 +718,10 @@ describe('Quick Filters refetch behavior', () => {
|
||||
),
|
||||
);
|
||||
|
||||
render(
|
||||
<TestQuickFilters
|
||||
quickFilterSource={QuickFiltersSource.LOGS_EXPLORER}
|
||||
config={[]}
|
||||
/>,
|
||||
);
|
||||
render(<TestQuickFilters signal={SIGNAL} config={[]} />);
|
||||
|
||||
await expect(
|
||||
screen.findByText('No filters found'),
|
||||
).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quick Filters field APIs', () => {
|
||||
it('sends the page signal for every filter, the infra Environment filter included', async () => {
|
||||
const requests: URLSearchParams[] = [];
|
||||
server.use(
|
||||
rest.get(fieldsValuesURL, (req, res, ctx) => {
|
||||
requests.push(req.url.searchParams);
|
||||
return res(ctx.status(200), ctx.json(quickFiltersFieldValuesResponse));
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<QuickFilters
|
||||
quickFilterSource={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={getHostsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={QuickFiltersFieldApis}
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(requests.map((params) => params.get('name'))).toContain(
|
||||
INFRA_MONITORING_ATTR_KEYS.DEPLOYMENT_ENVIRONMENT,
|
||||
),
|
||||
);
|
||||
const environment = requests.find(
|
||||
(params) =>
|
||||
params.get('name') === INFRA_MONITORING_ATTR_KEYS.DEPLOYMENT_ENVIRONMENT,
|
||||
);
|
||||
expect(environment?.get('signal')).toBe(TelemetrytypesSignalDTO.metrics);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { QuickfiltertypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export enum FiltersType {
|
||||
SLIDER = 'SLIDER',
|
||||
@@ -19,6 +19,15 @@ export enum SpecficFilterOperations {
|
||||
ONLY = 'ONLY',
|
||||
}
|
||||
|
||||
export enum SignalType {
|
||||
TRACES = 'traces',
|
||||
LOGS = 'logs',
|
||||
API_MONITORING = 'api_monitoring',
|
||||
EXCEPTIONS = 'exceptions',
|
||||
METER_EXPLORER = 'meter',
|
||||
AI_OBSERVABILITY = 'ai_observability',
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing export from signozhq/ui/checkbox, TODO(H4ad): Add and remove this type definition
|
||||
*/
|
||||
@@ -30,6 +39,7 @@ export interface IQuickFiltersConfig {
|
||||
attributeKey: BaseAutocompleteData;
|
||||
aggregateOperator?: string;
|
||||
aggregateAttribute?: string;
|
||||
dataSource?: DataSource;
|
||||
customRendererForValue?: (value: string) => JSX.Element;
|
||||
defaultOpen: boolean;
|
||||
}
|
||||
@@ -43,9 +53,11 @@ export interface QuickFilterChangeEventData {
|
||||
export interface IQuickFiltersProps {
|
||||
config: IQuickFiltersConfig[];
|
||||
handleFilterVisibilityChange?: () => void;
|
||||
quickFilterSource: QuickFiltersSource;
|
||||
source: QuickFiltersSource;
|
||||
onFilterChange?: (query: Query) => void;
|
||||
onQuickFilterChange?: (data: QuickFilterChangeEventData) => void;
|
||||
/** Pass to fetch quick filters for this signal; omit to use `config` as-is */
|
||||
signal?: SignalType;
|
||||
className?: string;
|
||||
showFilterCollapse?: boolean;
|
||||
showQueryName?: boolean;
|
||||
@@ -53,19 +65,15 @@ export interface IQuickFiltersProps {
|
||||
savedViewsHeader?: ReactNode;
|
||||
}
|
||||
|
||||
export const QuickFiltersSource = {
|
||||
LOGS_EXPLORER: QuickfiltertypesSourceDTO.logs,
|
||||
TRACES_EXPLORER: QuickfiltertypesSourceDTO.traces,
|
||||
EXCEPTIONS: QuickfiltertypesSourceDTO.exceptions,
|
||||
API_MONITORING: QuickfiltertypesSourceDTO.api_monitoring,
|
||||
METER_EXPLORER: QuickfiltertypesSourceDTO.meter,
|
||||
AI_OBSERVABILITY: QuickfiltertypesSourceDTO.ai_observability,
|
||||
/** No saved filter list: renders `config` as given. */
|
||||
INFRA_MONITORING: 'infra-monitoring',
|
||||
} as const;
|
||||
|
||||
export type QuickFiltersSource =
|
||||
(typeof QuickFiltersSource)[keyof typeof QuickFiltersSource];
|
||||
export enum QuickFiltersSource {
|
||||
LOGS_EXPLORER = 'logs-explorer',
|
||||
INFRA_MONITORING = 'infra-monitoring',
|
||||
TRACES_EXPLORER = 'traces-explorer',
|
||||
API_MONITORING = 'api-monitoring',
|
||||
EXCEPTIONS = 'exceptions',
|
||||
METER_EXPLORER = 'meter',
|
||||
AI_OBSERVABILITY = 'ai-observability',
|
||||
}
|
||||
|
||||
export type QuickFilterCheckboxUseFieldApis = {
|
||||
startUnixMilli: number;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TelemetrytypesFieldContextDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
import { fieldDataTypeToDataType } from 'utils/fieldDataType';
|
||||
|
||||
import { FiltersType, IQuickFiltersConfig } from './types';
|
||||
import { FiltersType, IQuickFiltersConfig, SignalType } from './types';
|
||||
|
||||
const FILTER_TITLE_MAP: Record<string, string> = {
|
||||
duration_nano: 'Duration',
|
||||
@@ -49,10 +50,11 @@ const getFilterType = (att: TelemetryFieldKey): FiltersType => {
|
||||
};
|
||||
|
||||
export const getFilterConfig = (
|
||||
signal?: SignalType,
|
||||
customFilters?: TelemetryFieldKey[],
|
||||
config?: IQuickFiltersConfig[],
|
||||
): IQuickFiltersConfig[] => {
|
||||
if (!customFilters?.length) {
|
||||
if (!customFilters?.length || !signal) {
|
||||
return config || [];
|
||||
}
|
||||
|
||||
@@ -61,6 +63,7 @@ export const getFilterConfig = (
|
||||
({
|
||||
type: getFilterType(att),
|
||||
title: getFilterName(att.name),
|
||||
dataSource: SIGNAL_DATA_SOURCE_MAP[signal],
|
||||
attributeKey: {
|
||||
id: att.name,
|
||||
key: att.name,
|
||||
|
||||
@@ -21,7 +21,6 @@ export enum LOCALSTORAGE {
|
||||
CHAT_SUPPORT = 'CHAT_SUPPORT',
|
||||
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
|
||||
DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES',
|
||||
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
|
||||
PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES',
|
||||
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
||||
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
|
||||
@@ -47,5 +46,4 @@ export enum LOCALSTORAGE {
|
||||
DASHBOARDS_LIST_VIEWS = 'DASHBOARDS_LIST_VIEWS',
|
||||
DASHBOARD_V2_PANEL_COLUMN_WIDTHS = 'DASHBOARD_V2_PANEL_COLUMN_WIDTHS',
|
||||
LLM_ATTRIBUTE_MAPPING_TEST_SPAN = 'LLM_ATTRIBUTE_MAPPING_TEST_SPAN',
|
||||
SAVED_VIEW_ENABLED = 'SAVED_VIEW_ENABLED',
|
||||
}
|
||||
|
||||
@@ -50,8 +50,6 @@ const ROUTES = {
|
||||
ROLE_EDIT: '/settings/roles/:roleId/edit',
|
||||
MEMBERS_SETTINGS: '/settings/members',
|
||||
SUPPORT: '/support',
|
||||
LOGS_SAVE_VIEWS: '/logs/saved-views',
|
||||
TRACES_SAVE_VIEWS: '/traces/saved-views',
|
||||
TRACES_FUNNELS: '/traces/funnels',
|
||||
TRACES_FUNNELS_DETAIL: '/traces/funnels/:funnelId',
|
||||
WORKSPACE_LOCKED: '/workspace-locked',
|
||||
@@ -69,7 +67,6 @@ const ROUTES = {
|
||||
MESSAGING_QUEUES_OVERVIEW: '/messaging-queues/overview',
|
||||
METRICS_EXPLORER: '/metrics-explorer/summary',
|
||||
METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer',
|
||||
METRICS_EXPLORER_VIEWS: '/metrics-explorer/views',
|
||||
METRICS_EXPLORER_VOLUME_CONTROL: '/metrics-explorer/volume-control',
|
||||
API_MONITORING_BASE: '/api-monitoring',
|
||||
API_MONITORING: '/api-monitoring/explorer',
|
||||
@@ -77,7 +74,6 @@ const ROUTES = {
|
||||
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',
|
||||
METER: '/meter',
|
||||
METER_EXPLORER: '/meter/explorer',
|
||||
METER_EXPLORER_VIEWS: '/meter/explorer/views',
|
||||
HOME_PAGE: '/',
|
||||
PUBLIC_DASHBOARD: '/public/dashboard/:dashboardId',
|
||||
SERVICE_ACCOUNTS_SETTINGS: '/settings/service-accounts',
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
.trigger {
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
z-index: 10;
|
||||
transition: transform 0.15s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { useCallback } from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Tooltip } from '@signozhq/ui/tooltip';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import Noz from 'components/Noz/Noz';
|
||||
import { NOZ_TOOLTIP_TITLE } from 'components/Noz/Noz.constants';
|
||||
|
||||
import { AIAssistantEvents, AIAssistantOpenSource } from '../events';
|
||||
import { normalizePage } from '../hooks/useAIAssistantAnalyticsContext';
|
||||
import {
|
||||
openAIAssistant,
|
||||
useAIAssistantStore,
|
||||
} from '../store/useAIAssistantStore';
|
||||
|
||||
import styles from './AIAssistantTrigger.module.scss';
|
||||
|
||||
/**
|
||||
* Floating action button anchored to the bottom-right of the content area.
|
||||
* Hidden when the panel is already open or when on the full-screen AI Assistant page.
|
||||
*/
|
||||
export default function AIAssistantTrigger(): JSX.Element | null {
|
||||
const { pathname } = useLocation();
|
||||
const isDrawerOpen = useAIAssistantStore((s) => s.isDrawerOpen);
|
||||
const isModalOpen = useAIAssistantStore((s) => s.isModalOpen);
|
||||
|
||||
const isFullScreenPage = !!matchPath(pathname, {
|
||||
path: ROUTES.AI_ASSISTANT,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
const handleOpen = useCallback((): void => {
|
||||
void logEvent(AIAssistantEvents.Opened, {
|
||||
source: AIAssistantOpenSource.Icon,
|
||||
currentPage: normalizePage(pathname),
|
||||
});
|
||||
openAIAssistant();
|
||||
}, [pathname]);
|
||||
|
||||
if (isDrawerOpen || isModalOpen || isFullScreenPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${styles.trigger} noz-wave`}>
|
||||
<Tooltip title={NOZ_TOOLTIP_TITLE}>
|
||||
<Button
|
||||
size="md"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
icon
|
||||
onClick={handleOpen}
|
||||
aria-label="Open Noz"
|
||||
>
|
||||
<Noz size={24} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './AIAssistantTrigger';
|
||||
export { default } from './AIAssistantTrigger';
|
||||
@@ -102,7 +102,7 @@ describe('resolvePageType', () => {
|
||||
expect(resolvePageType(ROUTES.METRICS_EXPLORER_EXPLORER, '')).toBe(
|
||||
PageTypeDTO.metrics_explorer,
|
||||
);
|
||||
expect(resolvePageType(ROUTES.METRICS_EXPLORER_VIEWS, '')).toBe(
|
||||
expect(resolvePageType('/metrics-explorer/views', '')).toBe(
|
||||
PageTypeDTO.metrics_explorer,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -235,7 +235,6 @@ describe('buildExplorerNavigationUrl', () => {
|
||||
{ queryType: 'builder' } as never,
|
||||
{
|
||||
[QueryParams.panelTypes]: PANEL_TYPES.LIST,
|
||||
[QueryParams.viewName]: 'My view',
|
||||
[QueryParams.viewKey]: 'view-1',
|
||||
},
|
||||
);
|
||||
@@ -259,7 +258,7 @@ describe('openSavedView', () => {
|
||||
expect(pushedUrl).toContain(ROUTES.LOGS_EXPLORER);
|
||||
const params = new URLSearchParams(pushedUrl.split('?')[1]);
|
||||
expect(params.get(QueryParams.viewKey)).toBe('"view-logs"');
|
||||
expect(params.get(QueryParams.viewName)).toBe('"View view-logs"');
|
||||
expect(params.get(QueryParams.viewName)).toBeNull();
|
||||
expect(params.get(QueryParams.panelTypes)).toBe('"list"');
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SavedViewSourcePage } from 'container/SavedViews/types';
|
||||
import { findSavedView } from 'container/SavedViews/utils/findSavedView';
|
||||
import { getSavedViewQuery } from 'container/SavedViews/utils/getSavedViewQuery';
|
||||
import { toSavedViewSource } from 'container/SavedViews/utils/toSavedViewSource';
|
||||
import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
|
||||
import { SOURCEPAGE_VS_ROUTES } from 'container/SavedViews/constants';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { History } from 'history';
|
||||
@@ -108,7 +108,6 @@ export function openSavedView(
|
||||
const query = getSavedViewQuery(view);
|
||||
const url = buildExplorerNavigationUrl(route, query, {
|
||||
[QueryParams.panelTypes]: view.spec.panelType as unknown as PANEL_TYPES,
|
||||
[QueryParams.viewName]: view.spec.displayName,
|
||||
[QueryParams.viewKey]: view.id,
|
||||
});
|
||||
history.push(url);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { matchPath } from 'react-router-dom';
|
||||
*
|
||||
* **URL-only.** All inputs come from `pathname` + `search`; we never reach
|
||||
* into Redux / context. SigNoz already encodes most page state into the URL
|
||||
* (`compositeQuery`, `startTime`/`endTime`, `viewName`/`viewKey`,
|
||||
* (`compositeQuery`, `startTime`/`endTime`, `viewKey`,
|
||||
* `variables`, `expandedWidgetId`, `activeLogId`, …), so URL parsing is
|
||||
* sufficient. Anything not in the URL is simply omitted from `metadata` —
|
||||
* the server applies its own defaults.
|
||||
@@ -288,7 +288,7 @@ export function getAutoContexts(
|
||||
|
||||
// ── Metrics ───────────────────────────────────────────────────────────────
|
||||
|
||||
// Metrics explorer — `/metrics-explorer` and sub-routes (summary, explorer, views).
|
||||
// Metrics explorer — `/metrics-explorer` and sub-routes (summary, explorer).
|
||||
if (
|
||||
matchPath(pathname, { path: ROUTES.METRICS_EXPLORER_BASE, exact: false })
|
||||
) {
|
||||
@@ -361,10 +361,6 @@ function collectSharedMetadata(
|
||||
if (viewKey) {
|
||||
out.savedViewId = viewKey;
|
||||
}
|
||||
const viewName = params.get(QueryParams.viewName);
|
||||
if (viewName) {
|
||||
out.savedViewName = viewName;
|
||||
}
|
||||
const variablesRaw = params.get(QueryParams.variables);
|
||||
if (variablesRaw) {
|
||||
try {
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Sentry from '@sentry/react';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import QuickFiltersLayout from 'components/QuickFilters/QuickFiltersLayout/QuickFiltersLayout';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
|
||||
import DomainList from './Domains/DomainList';
|
||||
@@ -24,7 +24,8 @@ function Explorer(): JSX.Element {
|
||||
showFilters
|
||||
quickFilterProps={{
|
||||
className: 'qf-api-monitoring',
|
||||
quickFilterSource: QuickFiltersSource.API_MONITORING,
|
||||
source: QuickFiltersSource.API_MONITORING,
|
||||
signal: SignalType.API_MONITORING,
|
||||
showQueryName: false,
|
||||
useFieldApis: quickFilterFieldApis,
|
||||
}}
|
||||
|
||||
@@ -91,6 +91,7 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -101,6 +102,7 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
dataType: DataTypes.String,
|
||||
type: 'resource',
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -111,6 +113,7 @@ export const ApiMonitoringQuickFiltersConfig: IQuickFiltersConfig[] = [
|
||||
dataType: DataTypes.String,
|
||||
type: 'tag',
|
||||
},
|
||||
dataSource: DataSource.TRACES,
|
||||
defaultOpen: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -72,40 +72,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-support-gateway {
|
||||
position: fixed;
|
||||
// Lifted above the bottom strip. Don't extend this pattern — new fixed-bottom
|
||||
// UI belongs in the bounded layout, not in another offset here.
|
||||
bottom: calc(20px + var(--bottom-strip-height, 0px));
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
|
||||
.chat-support-gateway-btn {
|
||||
max-width: 48px;
|
||||
width: 48px;
|
||||
max-height: 48px;
|
||||
height: 48px;
|
||||
padding: 12px;
|
||||
border-radius: 50%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: #f25733;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
color: white !important;
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
.chat-support-gateway-btn-icon {
|
||||
fill: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-credit-card-btn,
|
||||
.cancel-btn {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -28,7 +28,6 @@ import getUserLatestVersion from 'api/v1/version/getLatestVersion';
|
||||
import { AxiosError } from 'axios';
|
||||
import cx from 'classnames';
|
||||
import ChangelogModal from 'components/ChangelogModal/ChangelogModal';
|
||||
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import RefreshPaymentStatus from 'components/RefreshPaymentStatus/RefreshPaymentStatus';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
@@ -51,8 +50,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ChatSupportState, useChatSupport } from 'hooks/useChatSupport';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
import useTabVisibility from 'hooks/useTabFocus';
|
||||
import history from 'lib/history';
|
||||
import { isNull } from 'lodash-es';
|
||||
@@ -401,7 +398,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}, [pathname]);
|
||||
|
||||
const isToDisplayLayout = isLoggedIn;
|
||||
const isSavedViewEnabled = useSavedViewEnabled();
|
||||
|
||||
const routeKey = useMemo(() => getRouteKey(pathname), [pathname]);
|
||||
const pageTitle = t(routeKey);
|
||||
@@ -489,8 +485,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
}
|
||||
}, [isDarkMode]);
|
||||
|
||||
const chatSupport = useChatSupport();
|
||||
|
||||
// Listen for API warnings
|
||||
const handleWarning = (
|
||||
isSlow: boolean,
|
||||
@@ -834,9 +828,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
</LayoutContent>
|
||||
</Sentry.ErrorBoundary>
|
||||
|
||||
{isSavedViewEnabled && isToDisplayLayout && !renderFullScreen && (
|
||||
<BottomStrip />
|
||||
)}
|
||||
{isToDisplayLayout && !renderFullScreen && <BottomStrip />}
|
||||
</div>
|
||||
|
||||
{isLoggedIn && isAIAssistantEnabled && (
|
||||
@@ -847,9 +839,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
{chatSupport === ChatSupportState.NeedsCard && !isSavedViewEnabled && (
|
||||
<ChatSupportGateway />
|
||||
)}
|
||||
{showChangelogModal && changelog && (
|
||||
<ChangelogModal changelog={changelog} onClose={toggleChangelogModal} />
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Spin } from 'antd';
|
||||
import { TIMEZONE_DATA } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { UniversalYAxisUnit } from 'components/YAxisUnitSelector/types';
|
||||
import { getRandomColor } from 'container/ExplorerOptions/utils';
|
||||
import { AlertDetectionTypes } from 'container/FormAlertRules';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
@@ -54,7 +53,9 @@ export function getColorForThreshold(thresholdLabel: string): string {
|
||||
if (thresholdLabel === 'info') {
|
||||
return Color.BG_ROBIN_500;
|
||||
}
|
||||
return getRandomColor();
|
||||
const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
|
||||
const randomKey = colorKeys[Math.floor(Math.random() * colorKeys.length)];
|
||||
return Color[randomKey];
|
||||
}
|
||||
|
||||
export function parseGoTime(input: string): {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import ExplorerOptions, { ExplorerOptionsProps } from './ExplorerOptions';
|
||||
import { getExplorerToolBarVisibility } from './utils';
|
||||
|
||||
type ExplorerOptionsWrapperProps = Omit<
|
||||
ExplorerOptionsProps,
|
||||
'isExplorerOptionDrop'
|
||||
>;
|
||||
|
||||
function ExplorerOptionWrapper({
|
||||
disabled,
|
||||
query,
|
||||
isLoading,
|
||||
onExport,
|
||||
sourcepage,
|
||||
isOneChartPerQuery,
|
||||
splitedQueries,
|
||||
signalSource,
|
||||
handleChangeSelectedView,
|
||||
}: ExplorerOptionsWrapperProps): JSX.Element {
|
||||
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const toolbarVisibility = getExplorerToolBarVisibility(sourcepage);
|
||||
setIsExplorerOptionHidden(!toolbarVisibility);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ExplorerOptions
|
||||
disabled={disabled}
|
||||
query={query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
sourcepage={sourcepage}
|
||||
signalSource={signalSource}
|
||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||
isOneChartPerQuery={isOneChartPerQuery}
|
||||
splitedQueries={splitedQueries}
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExplorerOptionWrapper;
|
||||
@@ -1,234 +0,0 @@
|
||||
.explorer-options-container {
|
||||
position: fixed;
|
||||
// Lifted above the bottom strip. Don't extend this pattern — new fixed-bottom
|
||||
// UI belongs in the bounded layout, not in another offset here.
|
||||
bottom: var(--bottom-strip-height, 0px);
|
||||
left: calc(50% + 240px);
|
||||
transform: translate(calc(-50% - 120px), 0);
|
||||
transition: left 0.2s linear;
|
||||
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
background-color: transparent;
|
||||
|
||||
.multi-alert-button,
|
||||
.multi-dashboard-button {
|
||||
min-width: 130px;
|
||||
|
||||
.ant-select-selector {
|
||||
.ant-select-selection-placeholder {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__divider {
|
||||
--divider-vertical-margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-update {
|
||||
left: calc(50% - 72px) !important;
|
||||
}
|
||||
|
||||
.explorer-update {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: color-mix(in srgb, var(--l1-border) 60%, transparent);
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 4px;
|
||||
backdrop-filter: blur(20px);
|
||||
box-sizing: border-box;
|
||||
|
||||
.action-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-radius: 50px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l1-border);
|
||||
cursor: pointer;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.explorer-options {
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
cursor: default;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
z-index: 1;
|
||||
|
||||
.ant-select-selector {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
|
||||
.view-options,
|
||||
.actions {
|
||||
.info-icon {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
.ant-select-focused {
|
||||
border-color: transparent !important;
|
||||
|
||||
.ant-select-selector {
|
||||
border-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-select-selector {
|
||||
border: transparent !important;
|
||||
background-color: transparent !important;
|
||||
|
||||
.ant-select-selection-placeholder {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.app-content {
|
||||
&.collapsed {
|
||||
.explorer-options-container {
|
||||
left: calc(50% + 72px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.render-options {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 2px;
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.dot {
|
||||
margin-left: 6px;
|
||||
min-height: 6px;
|
||||
min-width: 6px;
|
||||
border-radius: 50%;
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
}
|
||||
|
||||
.save-view-modal {
|
||||
width: 384px !important;
|
||||
.ant-modal-content {
|
||||
padding: 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.ant-modal-header {
|
||||
padding: 16px;
|
||||
background: var(--l2-background);
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
.ant-modal-body {
|
||||
padding: 12px 16px 0px 16px;
|
||||
|
||||
.ant-typography {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 142.857% */
|
||||
}
|
||||
|
||||
.save-view-input {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.ant-color-picker-trigger {
|
||||
padding: 6px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
.ant-color-picker-color-block {
|
||||
border-radius: 50px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.ant-color-picker-color-block-inner {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 16px;
|
||||
margin: 0;
|
||||
.save-button {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
.title {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,48 +0,0 @@
|
||||
.explorer-option-droppable-container {
|
||||
position: fixed;
|
||||
// Lifted above the bottom strip. Don't extend this pattern — new fixed-bottom
|
||||
// UI belongs in the bounded layout, not in another offset here.
|
||||
bottom: var(--bottom-strip-height, 0px);
|
||||
width: -webkit-fill-available;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
// box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
|
||||
// backdrop-filter: blur(20px);
|
||||
|
||||
.explorer-actions-btn {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-right: 8px;
|
||||
|
||||
.action-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 10px 10px 0px 0px;
|
||||
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
|
||||
backdrop-filter: blur(20px);
|
||||
height: 24px !important;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.explorer-show-btn {
|
||||
border-radius: 6px 6px 0px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: color-mix(in srgb, var(--l1-border) 40%, transparent);
|
||||
box-shadow: 0px 4px 16px 0px rgba(0, 0, 0, 0.25);
|
||||
backdrop-filter: blur(20px);
|
||||
align-self: center;
|
||||
padding: 8px 12px;
|
||||
height: 24px !important;
|
||||
|
||||
.menu-bar {
|
||||
border-radius: 50px;
|
||||
background: var(--l3-background);
|
||||
height: 4px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { Disc3, X } from '@signozhq/icons';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { setExplorerToolBarVisibility } from './utils';
|
||||
|
||||
import './ExplorerOptionsHideArea.styles.scss';
|
||||
|
||||
interface DroppableAreaProps {
|
||||
viewName: string;
|
||||
isQueryUpdated: boolean;
|
||||
isExplorerOptionHidden?: boolean;
|
||||
sourcepage: DataSource;
|
||||
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
||||
handleClearSelect: () => void;
|
||||
onUpdateQueryHandler: () => void;
|
||||
isEditDeleteSupported: boolean;
|
||||
}
|
||||
|
||||
function ExplorerOptionsHideArea({
|
||||
viewName,
|
||||
isQueryUpdated,
|
||||
isExplorerOptionHidden,
|
||||
sourcepage,
|
||||
setIsExplorerOptionHidden,
|
||||
handleClearSelect,
|
||||
onUpdateQueryHandler,
|
||||
isEditDeleteSupported,
|
||||
}: DroppableAreaProps): JSX.Element {
|
||||
const handleShowExplorerOption = (): void => {
|
||||
if (setIsExplorerOptionHidden) {
|
||||
setIsExplorerOptionHidden(false);
|
||||
setExplorerToolBarVisibility(true, sourcepage);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="explorer-option-droppable-container">
|
||||
{isExplorerOptionHidden && (
|
||||
<>
|
||||
{viewName && (
|
||||
<div className="explorer-actions-btn">
|
||||
<Tooltip title="Clear this view">
|
||||
<Button
|
||||
onClick={handleClearSelect}
|
||||
className="action-btn"
|
||||
style={{ background: Color.BG_CHERRY_500 }}
|
||||
icon={<X size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
{isEditDeleteSupported && isQueryUpdated && (
|
||||
<Tooltip title="Update this View">
|
||||
<Button
|
||||
onClick={onUpdateQueryHandler}
|
||||
className="action-btn"
|
||||
style={{ background: Color.BG_ROBIN_500 }}
|
||||
icon={<Disc3 size={14} color={Color.BG_INK_500} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Button
|
||||
// style={{ alignSelf: 'center', marginRight: 'calc(10% - 20px)' }}
|
||||
className="explorer-show-btn"
|
||||
onClick={handleShowExplorerOption}
|
||||
data-testid="show-explorer-option"
|
||||
>
|
||||
<div className="menu-bar" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ExplorerOptionsHideArea.defaultProps = {
|
||||
isExplorerOptionHidden: undefined,
|
||||
setIsExplorerOptionHidden: undefined,
|
||||
};
|
||||
|
||||
export default ExplorerOptionsHideArea;
|
||||
@@ -1,391 +0,0 @@
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { MOCK_QUERY } from 'container/QueryTable/Drilldown/__tests__/mockTableData';
|
||||
import { ExportDashboard } from 'hooks/dashboard/useExportDashboards';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import {
|
||||
defaultFeatureFlags,
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
} from 'tests/test-utils';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { buildExportPanelLink } from 'pages/DashboardPage/DashboardContainer/PanelEditor/newPanelRoute';
|
||||
|
||||
import ExplorerOptionWrapper from '../ExplorerOptionWrapper';
|
||||
import { getExplorerToolBarVisibility } from '../utils';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useHistory: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../utils', () => ({
|
||||
getExplorerToolBarVisibility: jest.fn(),
|
||||
generateRGBAFromHex: jest.fn(() => 'rgba(0, 0, 0, 0.08)'),
|
||||
getRandomColor: jest.fn(() => '#000000'),
|
||||
saveNewViewHandler: jest.fn(),
|
||||
setExplorerToolBarVisibility: jest.fn(),
|
||||
DATASOURCE_VS_ROUTES: {},
|
||||
}));
|
||||
|
||||
const mockGetExplorerToolBarVisibility = jest.mocked(
|
||||
getExplorerToolBarVisibility,
|
||||
);
|
||||
|
||||
const mockUseHistory = jest.mocked(useHistory);
|
||||
|
||||
// Mock data
|
||||
const TEST_QUERY_ID = 'test-query-id';
|
||||
const TEST_DASHBOARD_ID = 'test-dashboard-id';
|
||||
const TEST_DASHBOARD_TITLE_2 = 'Test Dashboard for Export';
|
||||
const NEW_DASHBOARD_ID = 'new-dashboard-id';
|
||||
|
||||
// The export dialog talks to the generated Perses-spec dashboard endpoints.
|
||||
const V2_LIST_ENDPOINT = '*/api/v2/users/me/dashboards';
|
||||
const V2_CREATE_ENDPOINT = '*/api/v2/dashboards';
|
||||
|
||||
// "Create new dashboard" names the dashboard from the `new_dashboard_title` i18n
|
||||
// key; the test-utils react-i18next mock returns the key verbatim.
|
||||
const NEW_DASHBOARD_TITLE = 'new_dashboard_title';
|
||||
|
||||
// Use the existing mock query from the codebase
|
||||
const mockQuery: Query = {
|
||||
...MOCK_QUERY,
|
||||
id: TEST_QUERY_ID, // Override with our test ID
|
||||
} as Query;
|
||||
|
||||
const ADD_TO_DASHBOARD_BUTTON_NAME = /add to dashboard/i;
|
||||
|
||||
interface PickerDashboard {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/** Register the "list dashboards" endpoint the picker reads. */
|
||||
function mockDashboardList(dashboards: PickerDashboard[]): void {
|
||||
server.use(
|
||||
rest.get(V2_LIST_ENDPOINT, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
dashboards: dashboards.map(({ id, title }) => ({
|
||||
id,
|
||||
name: title,
|
||||
spec: { display: { name: title } },
|
||||
})),
|
||||
reservedKeywords: [],
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/** Register the "create dashboard" endpoint the "New dashboard" button hits. */
|
||||
function mockCreateDashboard(id: string): void {
|
||||
server.use(
|
||||
rest.post(V2_CREATE_ENDPOINT, (_req, res, ctx) =>
|
||||
res(ctx.status(201), ctx.json({ status: 'success', data: { id } })),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper function to render component with props
|
||||
const renderExplorerOptionWrapper = (
|
||||
overrides = {},
|
||||
): ReturnType<typeof render> => {
|
||||
const props = {
|
||||
disabled: false,
|
||||
query: mockQuery,
|
||||
isLoading: false,
|
||||
onExport: jest.fn() as jest.MockedFunction<
|
||||
(
|
||||
dashboard: ExportDashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void
|
||||
>,
|
||||
sourcepage: DataSource.LOGS,
|
||||
isOneChartPerQuery: false,
|
||||
splitedQueries: [],
|
||||
signalSource: 'test-signal',
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return render(
|
||||
<ExplorerOptionWrapper
|
||||
disabled={props.disabled}
|
||||
query={props.query}
|
||||
isLoading={props.isLoading}
|
||||
onExport={props.onExport}
|
||||
sourcepage={props.sourcepage}
|
||||
isOneChartPerQuery={props.isOneChartPerQuery}
|
||||
splitedQueries={props.splitedQueries}
|
||||
signalSource={props.signalSource}
|
||||
/>,
|
||||
undefined,
|
||||
{ appContextOverrides: { featureFlags: defaultFeatureFlags } },
|
||||
);
|
||||
};
|
||||
|
||||
describe('ExplorerOptionWrapper', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockGetExplorerToolBarVisibility.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('should navigate to alert creation page when "Create an Alert" is clicked in logs-explorer', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const mockPush = jest.fn();
|
||||
mockUseHistory.mockReturnValue({
|
||||
push: mockPush,
|
||||
} as unknown as ReturnType<typeof useHistory>);
|
||||
|
||||
renderExplorerOptionWrapper({ sourcepage: DataSource.LOGS });
|
||||
|
||||
const createAlertButton = screen.getByRole('button', {
|
||||
name: 'Create an Alert',
|
||||
});
|
||||
await user.click(createAlertButton);
|
||||
|
||||
expect(mockPush).toHaveBeenCalledTimes(1);
|
||||
const calledWith = mockPush.mock.calls[0][0] as string;
|
||||
const [path, search = ''] = calledWith.split('?');
|
||||
expect(path).toBe('/alerts/new');
|
||||
const params = new URLSearchParams(search);
|
||||
expect(params.has('compositeQuery')).toBe(true);
|
||||
});
|
||||
|
||||
describe('onExport functionality', () => {
|
||||
it('should call onExport when New Dashboard button is clicked in export modal', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const testOnExport = jest.fn() as jest.MockedFunction<
|
||||
(
|
||||
dashboard: ExportDashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void
|
||||
>;
|
||||
|
||||
mockCreateDashboard(NEW_DASHBOARD_ID);
|
||||
|
||||
renderExplorerOptionWrapper({ onExport: testOnExport });
|
||||
|
||||
// Find and click the "Add to Dashboard" button
|
||||
const addToDashboardButton = screen.getByRole('button', {
|
||||
name: ADD_TO_DASHBOARD_BUTTON_NAME,
|
||||
});
|
||||
await user.click(addToDashboardButton);
|
||||
|
||||
// Wait for the export modal to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click the "New dashboard" button
|
||||
const newDashboardButton = screen.getByTestId('export-panel-new-dashboard');
|
||||
await user.click(newDashboardButton);
|
||||
|
||||
// Wait for the API call to complete and onExport to be called
|
||||
await waitFor(() => {
|
||||
expect(testOnExport).toHaveBeenCalledWith(
|
||||
{ id: NEW_DASHBOARD_ID, title: NEW_DASHBOARD_TITLE },
|
||||
true,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onExport when selecting existing dashboard and clicking Export button', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const testOnExport = jest.fn() as jest.MockedFunction<
|
||||
(
|
||||
dashboard: ExportDashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void
|
||||
>;
|
||||
|
||||
// Mock existing dashboards with unique titles
|
||||
const dashboards: PickerDashboard[] = [
|
||||
{ id: 'dashboard-1', title: 'Dashboard 1' },
|
||||
{ id: 'dashboard-2', title: 'Dashboard 2' },
|
||||
];
|
||||
mockDashboardList(dashboards);
|
||||
|
||||
renderExplorerOptionWrapper({ onExport: testOnExport });
|
||||
|
||||
// Find and click the "Add to Dashboard" button
|
||||
const addToDashboardButton = screen.getByRole('button', {
|
||||
name: ADD_TO_DASHBOARD_BUTTON_NAME,
|
||||
});
|
||||
await user.click(addToDashboardButton);
|
||||
|
||||
// Wait for the export modal to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for the dashboard select to render AND finish loading. antd
|
||||
// disables the Select while the list request is in flight, and a click
|
||||
// on a disabled combobox is a no-op — so sync on the ready (enabled)
|
||||
// state before clicking.
|
||||
const modal = screen.getByRole('dialog');
|
||||
await waitFor(() => {
|
||||
const combobox = modal.querySelector('[role="combobox"]');
|
||||
expect(combobox).toBeTruthy();
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
const dashboardSelect = modal.querySelector(
|
||||
'[role="combobox"]',
|
||||
) as HTMLElement;
|
||||
expect(dashboardSelect).toBeInTheDocument();
|
||||
await user.click(dashboardSelect);
|
||||
|
||||
// Wait for the dropdown options to appear and select the first dashboard
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(dashboards[0].title)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on the first dashboard option
|
||||
const dashboardOption = screen.getByText(dashboards[0].title);
|
||||
await user.click(dashboardOption);
|
||||
|
||||
// Wait for the selection to be made and the export button to be enabled
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('export-panel-export')).not.toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
});
|
||||
|
||||
// Click the export button
|
||||
const exportButton = screen.getByTestId('export-panel-export');
|
||||
await user.click(exportButton);
|
||||
|
||||
// Wait for onExport to be called with the selected dashboard
|
||||
await waitFor(() => {
|
||||
expect(testOnExport).toHaveBeenCalledWith(
|
||||
{ id: dashboards[0].id, title: dashboards[0].title },
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should navigate to the panel editor via the export link without writing the dashboard', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
// Mock the safeNavigate function
|
||||
const mockSafeNavigate = jest.fn();
|
||||
|
||||
const panelTypeParam = PANEL_TYPES.TIME_SERIES;
|
||||
const query = mockQuery;
|
||||
|
||||
// Create a real handleExport function similar to LogsExplorerViews
|
||||
// Export navigates only; it must not write the dashboard (PR #8029).
|
||||
const handleExport = (dashboard: ExportDashboard | null): void => {
|
||||
if (!dashboard) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Call the real link builder (not mocked)
|
||||
const dashboardEditView = buildExportPanelLink({
|
||||
query,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: dashboard.id,
|
||||
});
|
||||
|
||||
// Simulate navigation
|
||||
mockSafeNavigate(dashboardEditView);
|
||||
};
|
||||
|
||||
// Mock existing dashboards
|
||||
const dashboards: PickerDashboard[] = [
|
||||
{ id: TEST_DASHBOARD_ID, title: TEST_DASHBOARD_TITLE_2 },
|
||||
];
|
||||
mockDashboardList(dashboards);
|
||||
|
||||
renderExplorerOptionWrapper({ onExport: handleExport });
|
||||
|
||||
// Find and click the "Add to Dashboard" button
|
||||
const addToDashboardButton = screen.getByRole('button', {
|
||||
name: ADD_TO_DASHBOARD_BUTTON_NAME,
|
||||
});
|
||||
await user.click(addToDashboardButton);
|
||||
|
||||
// Wait for the export modal to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Wait for the dashboard select to render AND finish loading (see note above).
|
||||
const modal = screen.getByRole('dialog');
|
||||
await waitFor(() => {
|
||||
const combobox = modal.querySelector('[role="combobox"]');
|
||||
expect(combobox).toBeTruthy();
|
||||
expect(combobox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
const dashboardSelect = modal.querySelector(
|
||||
'[role="combobox"]',
|
||||
) as HTMLElement;
|
||||
expect(dashboardSelect).toBeInTheDocument();
|
||||
await user.click(dashboardSelect);
|
||||
|
||||
// Wait for the dropdown options to appear and select the dashboard
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(dashboards[0].title)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Click on the dashboard option
|
||||
const dashboardOption = screen.getByText(dashboards[0].title);
|
||||
await user.click(dashboardOption);
|
||||
|
||||
// Wait for the selection to be made and the export button to be enabled
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('export-panel-export')).not.toHaveAttribute(
|
||||
'aria-disabled',
|
||||
'true',
|
||||
);
|
||||
});
|
||||
|
||||
// Click the export button
|
||||
const exportButton = screen.getByTestId('export-panel-export');
|
||||
await user.click(exportButton);
|
||||
|
||||
// Wait for the handleExport function to be called and navigation to occur
|
||||
await waitFor(() => {
|
||||
expect(mockSafeNavigate).toHaveBeenCalledTimes(1);
|
||||
// V2 panel-editor link: compositeQuery is double-encoded (see newPanelRoute).
|
||||
expect(mockSafeNavigate).toHaveBeenCalledWith(
|
||||
`/dashboard/${TEST_DASHBOARD_ID}/panel/new?panelKind=signoz%2FTimeSeriesPanel&compositeQuery=${encodeURIComponent(
|
||||
encodeURIComponent(JSON.stringify(query)),
|
||||
)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show export buttons when component is disabled', () => {
|
||||
const testOnExport = jest.fn() as jest.MockedFunction<
|
||||
(
|
||||
dashboard: ExportDashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void
|
||||
>;
|
||||
|
||||
renderExplorerOptionWrapper({ disabled: true, onExport: testOnExport });
|
||||
|
||||
// The "Add to Dashboard" button should be disabled
|
||||
const addToDashboardButton = screen.getByRole('button', {
|
||||
name: ADD_TO_DASHBOARD_BUTTON_NAME,
|
||||
});
|
||||
expect(addToDashboardButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
export enum PreservedViewsTypes {
|
||||
LOGS = 'logs',
|
||||
TRACES = 'traces',
|
||||
METRICS = 'metrics',
|
||||
METER = 'meter',
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SaveViewWithNameProps } from 'components/ExplorerCard/types';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import { ExplorerViews } from 'pages/LogsExplorer/utils';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
import { PreservedViewsTypes } from './constants';
|
||||
|
||||
export interface SaveNewViewHandlerProps {
|
||||
viewName: string;
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
sourcePage: DataSource | 'meter';
|
||||
extraData: SaveViewProps['extraData'];
|
||||
panelType: PANEL_TYPES | null;
|
||||
notifications: NotificationInstance;
|
||||
refetchAllView: SaveViewWithNameProps['refetchAllView'];
|
||||
saveViewAsync: UseMutateAsyncFunction<
|
||||
AxiosResponse<SaveViewPayloadProps>,
|
||||
Error,
|
||||
SaveViewProps,
|
||||
SaveViewPayloadProps
|
||||
>;
|
||||
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
setNewViewName: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export type PreservedViewType =
|
||||
| PreservedViewsTypes.LOGS
|
||||
| PreservedViewsTypes.TRACES
|
||||
| PreservedViewsTypes.METRICS
|
||||
| PreservedViewsTypes.METER;
|
||||
|
||||
export type PreservedViewsInLocalStorage = Partial<
|
||||
Record<PreservedViewType, { key: string; value: string }>
|
||||
>;
|
||||
|
||||
export type ChangeViewFunctionType = (
|
||||
view: ExplorerViews,
|
||||
querySearchParameters?: ICurrentQueryData,
|
||||
) => void;
|
||||
@@ -1,123 +0,0 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { SaveNewViewHandlerProps } from './types';
|
||||
|
||||
export const getRandomColor = (): string => {
|
||||
const colorKeys = Object.keys(Color) as (keyof typeof Color)[];
|
||||
const randomKey = colorKeys[Math.floor(Math.random() * colorKeys.length)];
|
||||
return Color[randomKey];
|
||||
};
|
||||
|
||||
export const DATASOURCE_VS_ROUTES: Record<DataSource, string> = {
|
||||
[DataSource.METRICS]: ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
[DataSource.TRACES]: ROUTES.TRACES_EXPLORER,
|
||||
[DataSource.LOGS]: ROUTES.LOGS_EXPLORER,
|
||||
};
|
||||
|
||||
export const saveNewViewHandler = ({
|
||||
saveViewAsync,
|
||||
refetchAllView,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
redirectWithQueryBuilderData,
|
||||
panelType,
|
||||
setNewViewName,
|
||||
}: SaveNewViewHandlerProps): void => {
|
||||
saveViewAsync(
|
||||
{
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage: sourcePage as DataSource,
|
||||
extraData,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
refetchAllView();
|
||||
redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), {
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewName]: viewName,
|
||||
[QueryParams.viewKey]: data.data.data,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'View Saved Successfully',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
onSettled: () => {
|
||||
handlePopOverClose();
|
||||
setNewViewName('');
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const generateRGBAFromHex = (hex: string, opacity: number): string =>
|
||||
`rgba(${parseInt(hex.slice(1, 3), 16)}, ${parseInt(
|
||||
hex.slice(3, 5),
|
||||
16,
|
||||
)}, ${parseInt(hex.slice(5, 7), 16)}, ${opacity})`;
|
||||
|
||||
export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
|
||||
try {
|
||||
const showExplorerToolbar = getLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
);
|
||||
if (showExplorerToolbar === null) {
|
||||
const parsedShowExplorerToolbar: {
|
||||
[DataSource.LOGS]: boolean;
|
||||
[DataSource.TRACES]: boolean;
|
||||
[DataSource.METRICS]: boolean;
|
||||
} = {
|
||||
[DataSource.METRICS]: true,
|
||||
[DataSource.TRACES]: true,
|
||||
[DataSource.LOGS]: true,
|
||||
};
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
JSON.stringify(parsedShowExplorerToolbar),
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar || '{}');
|
||||
return parsedShowExplorerToolbar[dataSource];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const setExplorerToolBarVisibility = (
|
||||
value: boolean,
|
||||
dataSource: string,
|
||||
): void => {
|
||||
try {
|
||||
const showExplorerToolbar = getLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
);
|
||||
if (showExplorerToolbar) {
|
||||
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar);
|
||||
parsedShowExplorerToolbar[dataSource] = value;
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
JSON.stringify(parsedShowExplorerToolbar),
|
||||
);
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
@@ -10,9 +10,8 @@ import {
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getSavedViewQuery } from 'container/SavedViews/utils/getSavedViewQuery';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
|
||||
import { SOURCEPAGE_VS_ROUTES } from 'container/SavedViews/constants';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
@@ -36,7 +35,6 @@ export default function SavedViews({
|
||||
loadingUserPreferences: boolean;
|
||||
}): JSX.Element {
|
||||
const { user } = useAppContext();
|
||||
const isSavedViewEnabled = useSavedViewEnabled();
|
||||
const [selectedEntity, setSelectedEntity] = useState<string>('logs');
|
||||
const [selectedEntityViews, setSelectedEntityViews] = useState<
|
||||
SavedviewtypesSavedViewDTO[]
|
||||
@@ -104,7 +102,6 @@ export default function SavedViews({
|
||||
view.spec.panelType,
|
||||
{
|
||||
query: getSavedViewQuery(view),
|
||||
viewName: view.spec.displayName,
|
||||
viewKey: view.id,
|
||||
},
|
||||
SOURCEPAGE_VS_ROUTES[selectedEntity],
|
||||
@@ -117,19 +114,6 @@ export default function SavedViews({
|
||||
}
|
||||
}, [hasSavedViews, onUpdateChecklistDoneItem, loadingUserPreferences]);
|
||||
|
||||
const footerLink = useMemo(() => {
|
||||
if (selectedEntity === 'logs') {
|
||||
return ROUTES.LOGS_SAVE_VIEWS;
|
||||
}
|
||||
if (selectedEntity === 'traces') {
|
||||
return ROUTES.TRACES_SAVE_VIEWS;
|
||||
}
|
||||
if (selectedEntity === 'metrics') {
|
||||
return ROUTES.METRICS_EXPLORER_VIEWS;
|
||||
}
|
||||
return '';
|
||||
}, [selectedEntity]);
|
||||
|
||||
const getStartedLink = useMemo(() => {
|
||||
if (selectedEntity === 'logs') {
|
||||
return ROUTES.LOGS_EXPLORER;
|
||||
@@ -352,26 +336,6 @@ export default function SavedViews({
|
||||
<Card.Content>
|
||||
{selectedEntityViews.length > 0 ? renderSavedViews() : emptyStateCard()}
|
||||
</Card.Content>
|
||||
|
||||
{!isSavedViewEnabled && selectedEntityViews.length > 0 && (
|
||||
<Card.Footer>
|
||||
<div className="services-footer home-data-card-footer">
|
||||
<Link to={footerLink}>
|
||||
<Button
|
||||
type="link"
|
||||
className="periscope-btn link learn-more-link"
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: All saved views clicked', {
|
||||
entity: selectedEntity,
|
||||
});
|
||||
}}
|
||||
>
|
||||
All Views <ArrowRight size={12} />
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Card.Footer>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ function Hosts(): JSX.Element {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<QuickFilters
|
||||
quickFilterSource={QuickFiltersSource.INFRA_MONITORING}
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={getHostsQuickFiltersConfig()}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={{
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TriangleAlert } from '@signozhq/icons';
|
||||
import TanStackTable from 'components/TanStackTableView';
|
||||
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
const HOSTNAME_DOCS_URL =
|
||||
'https://signoz.io/docs/infrastructure-monitoring/hostmetrics/#host-name-is-blankempty';
|
||||
|
||||
@@ -73,6 +74,7 @@ export function getHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.SYSTEM_CPU_LOAD_AVERAGE_15M,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -85,6 +87,7 @@ export function getHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.SYSTEM_CPU_LOAD_AVERAGE_15M,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -321,7 +321,7 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
</div>
|
||||
{selectedCategoryConfig && (
|
||||
<QuickFilters
|
||||
quickFilterSource={QuickFiltersSource.INFRA_MONITORING}
|
||||
source={QuickFiltersSource.INFRA_MONITORING}
|
||||
config={selectedCategoryConfig}
|
||||
handleFilterVisibilityChange={handleFilterVisibilityChange}
|
||||
useFieldApis={selectedCategoryUseFieldApis}
|
||||
|
||||
@@ -228,6 +228,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -241,6 +242,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -254,6 +256,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -267,6 +270,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -280,6 +284,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -293,6 +298,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -306,6 +312,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -319,6 +326,7 @@ export function GetPodsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -347,6 +355,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -360,6 +369,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -373,6 +383,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -386,6 +397,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -399,6 +411,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -412,6 +425,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -425,6 +439,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -438,6 +453,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -451,6 +467,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -464,6 +481,7 @@ export function GetContainersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.CONTAINER_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: false,
|
||||
},
|
||||
{
|
||||
@@ -492,6 +510,7 @@ export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -505,6 +524,7 @@ export function GetNodesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -533,6 +553,7 @@ export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -546,6 +567,7 @@ export function GetNamespaceQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -574,6 +596,7 @@ export function GetClustersQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -602,6 +625,7 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_CAPACITY,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -615,6 +639,7 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_CAPACITY,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -628,6 +653,7 @@ export function GetVolumesQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_VOLUME_CAPACITY,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -656,6 +682,7 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -669,6 +696,7 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -682,6 +710,7 @@ export function GetDeploymentsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -710,6 +739,7 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -723,6 +753,7 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -736,6 +767,7 @@ export function GetStatefulsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -764,6 +796,7 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -776,6 +809,7 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -788,6 +822,7 @@ export function GetDaemonsetsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -816,6 +851,7 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -828,6 +864,7 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
@@ -840,6 +877,7 @@ export function GetJobsQuickFiltersConfig(): IQuickFiltersConfig[] {
|
||||
},
|
||||
aggregateOperator: 'noop',
|
||||
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
|
||||
dataSource: DataSource.METRICS,
|
||||
defaultOpen: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
|
||||
.traces-explorer-views {
|
||||
padding: 8px;
|
||||
padding-bottom: 60px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.ant-tabs-tabpane {
|
||||
|
||||
@@ -3,11 +3,10 @@ import { useQueryClient } from 'react-query';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
||||
import QueryCancelledPlaceholder from 'components/QueryCancelledPlaceholder';
|
||||
import QuickFiltersLayout from 'components/QuickFilters/QuickFiltersLayout/QuickFiltersLayout';
|
||||
import { useSignalFieldApis } from 'components/QuickFilters/hooks/useSignalFieldApis';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
|
||||
import WarningPopover from 'components/WarningPopover/WarningPopover';
|
||||
import { initialQueryAIWithType } from 'constants/queryBuilder';
|
||||
import { usePageActions } from 'container/AIAssistant/pageActions/usePageActions';
|
||||
@@ -193,7 +192,8 @@ function Explorer(): JSX.Element {
|
||||
onToggleFilters={(): void => setOpen(!isOpen)}
|
||||
quickFilterProps={{
|
||||
className: 'qf-traces-explorer',
|
||||
quickFilterSource: QuickFiltersSource.AI_OBSERVABILITY,
|
||||
source: QuickFiltersSource.AI_OBSERVABILITY,
|
||||
signal: SignalType.AI_OBSERVABILITY,
|
||||
useFieldApis: quickFiltersFieldApis,
|
||||
}}
|
||||
>
|
||||
@@ -225,11 +225,9 @@ function Explorer(): JSX.Element {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>
|
||||
<div className="query-section-container">
|
||||
<QuerySection />
|
||||
</div>
|
||||
</ExplorerCard>
|
||||
<div className="query-section-container">
|
||||
<QuerySection />
|
||||
</div>
|
||||
|
||||
<div className="traces-explorer-views">
|
||||
{isCancelled && (
|
||||
|
||||
@@ -6,7 +6,7 @@ import FieldsSelector from 'components/FieldsSelector';
|
||||
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
|
||||
import { MAX_LOGS_LIST_SIZE } from 'constants/liveTail';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import GoToTop from 'container/GoToTop';
|
||||
import { useOptionsMenu } from 'container/OptionsMenu';
|
||||
import { LOGS_REQUIRED_COLUMNS } from 'container/OptionsMenu/constants';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
export interface ILiveLogsLog {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import { DataViewer } from 'periscope/components/DataViewer';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ import { useCallback, useMemo } from 'react';
|
||||
import { CircleMinus, CirclePlus, Layers, RefreshCw } from '@signozhq/icons';
|
||||
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { ICurrentQueryData } from 'hooks/useHandleExplorerTabChange';
|
||||
import {
|
||||
ChangeViewFunctionType,
|
||||
ICurrentQueryData,
|
||||
} from 'hooks/useHandleExplorerTabChange';
|
||||
import { ExplorerViews } from 'pages/LogsExplorer/utils';
|
||||
import {
|
||||
FieldContext,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import APIError from 'types/api/error';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
@@ -16,7 +16,6 @@ import setToLocalstorage from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
initialFilters,
|
||||
@@ -24,8 +23,7 @@ import {
|
||||
PANEL_TYPES,
|
||||
} from 'constants/queryBuilder';
|
||||
import { DEFAULT_PER_PAGE_VALUE } from 'container/Controls/config';
|
||||
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
||||
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
|
||||
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
|
||||
import GoToTop from 'container/GoToTop';
|
||||
import LogsExplorerChart from 'container/LogsExplorerChart';
|
||||
import LogsExplorerList from 'container/LogsExplorerList';
|
||||
@@ -39,13 +37,9 @@ import {
|
||||
} from 'container/LogsExplorerViews/explorerUtils';
|
||||
import ExplorerActions from 'container/ExplorerActions/ExplorerActions';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { ExportDashboard } from 'hooks/dashboard/useExportDashboards';
|
||||
import { useGetExportToDashboardLink } from 'hooks/dashboard/useGetExportToDashboardLink';
|
||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||
import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQueryRange';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { useSavedViewEnabled } from 'hooks/useSavedViewEnabled';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import useUrlYAxisUnit from 'hooks/useUrlYAxisUnit';
|
||||
import { isEmpty, isUndefined } from 'lodash-es';
|
||||
@@ -59,7 +53,6 @@ import { Filter } from 'types/api/v5/queryRange';
|
||||
import { QueryDataV3 } from 'types/api/widgets/getQuery';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { v4 } from 'uuid';
|
||||
import { getListOrderBy, setListOrderBy } from 'utils/explorerUtils';
|
||||
|
||||
import LogsActionsContainer from './LogsActionsContainer';
|
||||
@@ -82,10 +75,6 @@ function LogsExplorerViewsContainer({
|
||||
showLiveLogs: boolean;
|
||||
handleChangeSelectedView: ChangeViewFunctionType;
|
||||
}): JSX.Element {
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const isSavedViewEnabled = useSavedViewEnabled();
|
||||
const getExportToDashboardLink = useGetExportToDashboardLink();
|
||||
|
||||
const [showFrequencyChart, setShowFrequencyChart] = useState(
|
||||
() => getFromLocalstorage(LOCALSTORAGE.SHOW_FREQUENCY_CHART) === 'true',
|
||||
);
|
||||
@@ -276,49 +265,6 @@ function LogsExplorerViewsContainer({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data?.payload]);
|
||||
|
||||
const handleExport = useCallback(
|
||||
(dashboard: ExportDashboard | null, isNewDashboard?: boolean): void => {
|
||||
if (!dashboard || !selectedPanelType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panelTypeParam = AVAILABLE_EXPORT_PANEL_TYPES.includes(
|
||||
selectedPanelType,
|
||||
)
|
||||
? selectedPanelType
|
||||
: PANEL_TYPES.TIME_SERIES;
|
||||
|
||||
const widgetId = v4();
|
||||
|
||||
if (!exportDefaultQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
logEvent('Logs Explorer: Add to dashboard successful', {
|
||||
panelType: selectedPanelType,
|
||||
isNewDashboard,
|
||||
dashboardName: dashboard?.title,
|
||||
});
|
||||
|
||||
const dashboardEditView = getExportToDashboardLink({
|
||||
query: exportDefaultQuery,
|
||||
panelType: panelTypeParam,
|
||||
dashboardId: dashboard.id,
|
||||
widgetId,
|
||||
});
|
||||
|
||||
if (dashboardEditView) {
|
||||
safeNavigate(dashboardEditView);
|
||||
}
|
||||
},
|
||||
[
|
||||
safeNavigate,
|
||||
exportDefaultQuery,
|
||||
selectedPanelType,
|
||||
getExportToDashboardLink,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const currentData = data?.payload?.data?.newResult?.data?.result || [];
|
||||
if (currentData.length > 0 && currentData[0].list) {
|
||||
@@ -517,16 +463,6 @@ function LogsExplorerViewsContainer({
|
||||
</div>
|
||||
|
||||
<GoToTop />
|
||||
|
||||
{!isSavedViewEnabled && (
|
||||
<ExplorerOptionWrapper
|
||||
disabled={!stagedQuery}
|
||||
query={exportDefaultQuery}
|
||||
onExport={handleExport}
|
||||
sourcepage={DataSource.LOGS}
|
||||
handleChangeSelectedView={handleChangeSelectedView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -138,13 +138,6 @@ jest.mock(
|
||||
return <div>MockQueryBuilderSearchV2</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'container/ExplorerOptions/ExplorerOptionWrapper',
|
||||
() =>
|
||||
function MockExplorerOptionWrapper(): JSX.Element {
|
||||
return <div>MockExplorerOptionWrapper</div>;
|
||||
},
|
||||
);
|
||||
jest.mock(
|
||||
'components/QuickFilters/QuickFilters',
|
||||
() =>
|
||||
@@ -416,7 +409,6 @@ function LogsExplorerWithMockContext({
|
||||
updateQueriesData: (): Query => initialQueriesMap.logs,
|
||||
initQueryBuilderData: noop,
|
||||
handleOnUnitsChange: noop,
|
||||
isStagedQueryUpdated: (): boolean => false,
|
||||
}),
|
||||
[currentQuery, stagedQuery, handleRunQuery],
|
||||
);
|
||||
|
||||
@@ -200,5 +200,4 @@ export const mockQueryBuilderContextValue = {
|
||||
updateQueriesData: (): Query => initialQueriesMap.logs,
|
||||
initQueryBuilderData: noop,
|
||||
handleOnUnitsChange: noop,
|
||||
isStagedQueryUpdated: (): boolean => false,
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user