mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-18 04:21:31 +08:00

* feat: added empty states for list, trace and timeSeried view in traces * feat: test case skip * feat: fixed import order * feat: added utm parameter link * feat: added strings * feat: resovled comments * feat: added common doclinks util * feat: test case updated:
284 lines
8.0 KiB
TypeScript
284 lines
8.0 KiB
TypeScript
import './TracesExplorer.styles.scss';
|
|
|
|
import { FilterOutlined } from '@ant-design/icons';
|
|
import * as Sentry from '@sentry/react';
|
|
import { Button, Card, Tabs, Tooltip } from 'antd';
|
|
import axios from 'axios';
|
|
import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
|
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
|
import { AVAILABLE_EXPORT_PANEL_TYPES } from 'constants/panelTypes';
|
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
|
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
|
|
import ExportPanel from 'container/ExportPanel';
|
|
import { useOptionsMenu } from 'container/OptionsMenu';
|
|
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
|
|
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
|
|
import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs';
|
|
import QuerySection from 'container/TracesExplorer/QuerySection';
|
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
|
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
|
|
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
|
|
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
|
|
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
|
import { useNotifications } from 'hooks/useNotifications';
|
|
import history from 'lib/history';
|
|
import { cloneDeep, isEmpty, set } from 'lodash-es';
|
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
import { Dashboard } from 'types/api/dashboard/getAll';
|
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
|
import { DataSource } from 'types/common/queryBuilder';
|
|
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
|
import { v4 } from 'uuid';
|
|
|
|
import { Filter } from './Filter/Filter';
|
|
import { ActionsWrapper, Container } from './styles';
|
|
import { getTabsItems } from './utils';
|
|
|
|
function TracesExplorer(): JSX.Element {
|
|
const { notifications } = useNotifications();
|
|
|
|
const {
|
|
currentQuery,
|
|
panelType,
|
|
updateAllQueriesOperators,
|
|
handleRunQuery,
|
|
stagedQuery,
|
|
} = useQueryBuilder();
|
|
|
|
const { options } = useOptionsMenu({
|
|
storageKey: LOCALSTORAGE.TRACES_LIST_OPTIONS,
|
|
dataSource: DataSource.TRACES,
|
|
aggregateOperator: 'noop',
|
|
initialOptions: {
|
|
selectColumns: defaultSelectedColumns,
|
|
},
|
|
});
|
|
|
|
const currentPanelType = useGetPanelTypesQueryParam();
|
|
|
|
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
|
|
|
const currentTab = panelType || PANEL_TYPES.LIST;
|
|
|
|
const listQuery = useMemo(() => {
|
|
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
|
|
|
|
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
|
|
}, [stagedQuery]);
|
|
|
|
const isMultipleQueries = useMemo(
|
|
() =>
|
|
currentQuery.builder.queryData.length > 1 ||
|
|
currentQuery.builder.queryFormulas.length > 0,
|
|
[currentQuery],
|
|
);
|
|
|
|
const isGroupByExist = useMemo(() => {
|
|
const groupByCount: number = currentQuery.builder.queryData.reduce<number>(
|
|
(acc, query) => acc + query.groupBy.length,
|
|
0,
|
|
);
|
|
|
|
return groupByCount > 0;
|
|
}, [currentQuery]);
|
|
|
|
const defaultQuery = useMemo(() => {
|
|
const query = updateAllQueriesOperators(
|
|
initialQueriesMap.traces,
|
|
PANEL_TYPES.LIST,
|
|
DataSource.TRACES,
|
|
);
|
|
|
|
return {
|
|
...query,
|
|
builder: {
|
|
...query.builder,
|
|
queryData: [
|
|
{
|
|
...query.builder.queryData[0],
|
|
orderBy: [{ columnName: 'timestamp', order: 'desc' }],
|
|
},
|
|
],
|
|
},
|
|
};
|
|
}, [updateAllQueriesOperators]);
|
|
|
|
const tabsItems = getTabsItems({
|
|
isListViewDisabled: isMultipleQueries || isGroupByExist,
|
|
isFilterApplied: !isEmpty(listQuery?.filters.items),
|
|
});
|
|
|
|
const exportDefaultQuery = useMemo(
|
|
() =>
|
|
updateAllQueriesOperators(
|
|
currentQuery || initialQueriesMap.traces,
|
|
PANEL_TYPES.TIME_SERIES,
|
|
DataSource.TRACES,
|
|
),
|
|
[currentQuery, updateAllQueriesOperators],
|
|
);
|
|
|
|
const { mutate: updateDashboard, isLoading } = useUpdateDashboard();
|
|
|
|
const getUpdatedQueryForExport = (): Query => {
|
|
const updatedQuery = cloneDeep(currentQuery);
|
|
|
|
set(
|
|
updatedQuery,
|
|
'builder.queryData[0].selectColumns',
|
|
options.selectColumns,
|
|
);
|
|
|
|
return updatedQuery;
|
|
};
|
|
|
|
const handleExport = useCallback(
|
|
(dashboard: Dashboard | null): void => {
|
|
if (!dashboard || !panelType) return;
|
|
|
|
const panelTypeParam = AVAILABLE_EXPORT_PANEL_TYPES.includes(panelType)
|
|
? panelType
|
|
: PANEL_TYPES.TIME_SERIES;
|
|
|
|
const widgetId = v4();
|
|
|
|
const query =
|
|
panelType === PANEL_TYPES.LIST
|
|
? getUpdatedQueryForExport()
|
|
: exportDefaultQuery;
|
|
|
|
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
|
dashboard,
|
|
query,
|
|
widgetId,
|
|
panelTypeParam,
|
|
options.selectColumns,
|
|
);
|
|
|
|
updateDashboard(updatedDashboard, {
|
|
onSuccess: (data) => {
|
|
if (data.error) {
|
|
const message =
|
|
data.error === 'feature usage exceeded' ? (
|
|
<span>
|
|
Panel limit exceeded for {DataSource.TRACES} in community edition.
|
|
Please checkout our paid plans{' '}
|
|
<a
|
|
href="https://signoz.io/pricing/?utm_source=product&utm_medium=dashboard-limit"
|
|
rel="noreferrer noopener"
|
|
target="_blank"
|
|
>
|
|
here
|
|
</a>
|
|
</span>
|
|
) : (
|
|
data.error
|
|
);
|
|
notifications.error({
|
|
message,
|
|
});
|
|
|
|
return;
|
|
}
|
|
const dashboardEditView = generateExportToDashboardLink({
|
|
query,
|
|
panelType: panelTypeParam,
|
|
dashboardId: data.payload?.uuid || '',
|
|
widgetId,
|
|
});
|
|
|
|
history.push(dashboardEditView);
|
|
},
|
|
onError: (error) => {
|
|
if (axios.isAxiosError(error)) {
|
|
notifications.error({
|
|
message: error.message,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
},
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[exportDefaultQuery, notifications, panelType, updateDashboard],
|
|
);
|
|
|
|
useShareBuilderUrl(defaultQuery);
|
|
|
|
useEffect(() => {
|
|
const shouldChangeView = isMultipleQueries || isGroupByExist;
|
|
|
|
if (
|
|
(currentTab === PANEL_TYPES.LIST || currentTab === PANEL_TYPES.TRACE) &&
|
|
shouldChangeView
|
|
) {
|
|
handleExplorerTabChange(currentPanelType || PANEL_TYPES.TIME_SERIES);
|
|
}
|
|
}, [
|
|
currentTab,
|
|
isMultipleQueries,
|
|
isGroupByExist,
|
|
handleExplorerTabChange,
|
|
currentPanelType,
|
|
]);
|
|
const [isOpen, setOpen] = useState<boolean>(true);
|
|
|
|
return (
|
|
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
|
<div className="trace-explorer-page">
|
|
<Card className="filter" hidden={!isOpen}>
|
|
<Filter setOpen={setOpen} />
|
|
</Card>
|
|
<Card className="trace-explorer">
|
|
<div className={`trace-explorer-header ${isOpen ? 'single-child' : ''}`}>
|
|
{!isOpen && (
|
|
<Tooltip title="Expand filters" placement="right">
|
|
<Button
|
|
onClick={(): void => setOpen(!isOpen)}
|
|
className="filter-outlined-btn"
|
|
>
|
|
<FilterOutlined />
|
|
</Button>
|
|
</Tooltip>
|
|
)}
|
|
<div className="trace-explorer-run-query">
|
|
<RightToolbarActions onStageRunQuery={handleRunQuery} />
|
|
<DateTimeSelector showAutoRefresh showOldExplorerCTA />
|
|
</div>
|
|
</div>
|
|
<ExplorerCard sourcepage={DataSource.TRACES}>
|
|
<QuerySection />
|
|
</ExplorerCard>
|
|
|
|
<Container className="traces-explorer-views">
|
|
<ActionsWrapper>
|
|
<ExportPanel
|
|
query={exportDefaultQuery}
|
|
isLoading={isLoading}
|
|
onExport={handleExport}
|
|
/>
|
|
</ActionsWrapper>
|
|
|
|
<Tabs
|
|
defaultActiveKey={currentTab}
|
|
activeKey={currentTab}
|
|
items={tabsItems}
|
|
onChange={handleExplorerTabChange}
|
|
/>
|
|
</Container>
|
|
<ExplorerOptionWrapper
|
|
disabled={!stagedQuery}
|
|
query={exportDefaultQuery}
|
|
isLoading={isLoading}
|
|
sourcepage={DataSource.TRACES}
|
|
onExport={handleExport}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
</Sentry.ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
export default TracesExplorer;
|