feat: update explorer views

This commit is contained in:
Yunus M 2025-06-04 16:37:18 +05:30 committed by SagarRajput-7
parent 6deb75ff46
commit bde078472b
33 changed files with 719 additions and 810 deletions

View File

@ -1,5 +0,0 @@
.logs-qb {
display: flex;
flex-direction: row;
gap: 8px;
}

View File

@ -1,79 +0,0 @@
import './LogsQB.styles.scss';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { Formula } from 'container/QueryBuilder/components/Formula/Formula';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo } from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { QueryBuilderV2Props } from '../QueryBuilderV2';
import QueryFooter from '../QueryV2/QueryFooter/QueryFooter';
import { QueryV2 } from '../QueryV2/QueryV2';
export const LogsQB = memo(function LogsQB({
filterConfigs,
}: QueryBuilderV2Props): JSX.Element {
const version = ENTITY_VERSION_V4;
const { currentQuery, addNewFormula, addNewBuilderQuery } = useQueryBuilder();
return (
<div className="logs-qb">
<div className="qb-content-container">
{currentQuery.builder.queryData.map((query, index) => (
<QueryV2
key={query.queryName}
index={index}
query={query}
filterConfigs={filterConfigs}
version={version}
isAvailableToDisable={false}
queryVariant="static"
source={DataSource.LOGS}
/>
))}
{currentQuery.builder.queryFormulas.length > 0 && (
<div className="qb-formulas-container">
{currentQuery.builder.queryFormulas.map((formula, index) => {
const query =
currentQuery.builder.queryData[index] ||
currentQuery.builder.queryData[0];
return (
<div key={formula.queryName} className="qb-formula">
<Formula
filterConfigs={filterConfigs}
query={query}
formula={formula}
index={index}
isAdditionalFilterEnable={false}
/>
</div>
);
})}
</div>
)}
<QueryFooter
addNewBuilderQuery={addNewBuilderQuery}
addNewFormula={addNewFormula}
/>
</div>
<div className="query-names-section">
{currentQuery.builder.queryData.map((query) => (
<div key={query.queryName} className="query-name">
{query.queryName}
</div>
))}
{currentQuery.builder.queryFormulas.map((formula) => (
<div key={formula.queryName} className="formula-name">
{formula.queryName}
</div>
))}
</div>
</div>
);
});

View File

@ -1,19 +0,0 @@
.metrics-qb {
display: flex;
flex-direction: row;
gap: 8px;
border-bottom: 1px solid var(--Slate-400, #1d212d);
.query-v2 {
.qb-entity-options {
.options {
.query-name {
&::before {
height: 306px !important;
}
}
}
}
}
}

View File

@ -1,89 +0,0 @@
import './MetricsQB.styles.scss';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { Formula } from 'container/QueryBuilder/components/Formula/Formula';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { DataSource } from 'types/common/queryBuilder';
import { QueryBuilderV2Props } from '../QueryBuilderV2';
import QueryFooter from '../QueryV2/QueryFooter/QueryFooter';
import { QueryV2 } from '../QueryV2/QueryV2';
function MetricsQB({ filterConfigs }: QueryBuilderV2Props): JSX.Element {
const version = ENTITY_VERSION_V4;
const { currentQuery, addNewFormula, addNewBuilderQuery } = useQueryBuilder();
const { isMetricsDataSource } = useQueryOperations({
index: 0,
query: currentQuery.builder.queryData[0],
filterConfigs,
isListViewPanel: false,
entityVersion: version,
});
console.log('isMetricsDataSource', isMetricsDataSource);
return (
<div className="metrics-qb">
<div className="qb-content-container">
{currentQuery.builder.queryData.map((query, index) => (
<QueryV2
key={query.queryName}
index={index}
query={query}
filterConfigs={filterConfigs}
version={version}
isAvailableToDisable={false}
queryVariant="static"
source={DataSource.METRICS}
/>
))}
{currentQuery.builder.queryFormulas.length > 0 && (
<div className="qb-formulas-container">
{currentQuery.builder.queryFormulas.map((formula, index) => {
const query =
currentQuery.builder.queryData[index] ||
currentQuery.builder.queryData[0];
return (
<div key={formula.queryName} className="qb-formula">
<Formula
filterConfigs={filterConfigs}
query={query}
formula={formula}
index={index}
isAdditionalFilterEnable={false} // TODO: Need to enable this
/>
</div>
);
})}
</div>
)}
<QueryFooter
addNewBuilderQuery={addNewBuilderQuery}
addNewFormula={addNewFormula}
/>
</div>
<div className="query-names-section">
{currentQuery.builder.queryData.map((query) => (
<div key={query.queryName} className="query-name">
{query.queryName}
</div>
))}
{currentQuery.builder.queryFormulas.map((formula) => (
<div key={formula.queryName} className="formula-name">
{formula.queryName}
</div>
))}
</div>
</div>
);
}
export default MetricsQB;

View File

@ -1,13 +1,10 @@
.query-builder-v2 { .query-builder-v2 {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
gap: 1rem; gap: 4px;
width: 100%; width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 4px;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif; 'Helvetica Neue', sans-serif;
@ -160,6 +157,7 @@
margin-left: 32px; margin-left: 32px;
padding-bottom: 16px; padding-bottom: 16px;
padding-left: 8px;
.qb-formula { .qb-formula {
.ant-row { .ant-row {
@ -343,6 +341,19 @@
} }
} }
} }
.query-data-source {
margin-left: 8px;
.ant-select-selector {
min-width: 120px;
border-radius: 2px;
border: 1px solid var(--Slate-400, #1d212d);
background: var(--Ink-300, #16181d);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
}
} }
.qb-search-container { .qb-search-container {

View File

@ -1,108 +1,119 @@
import './QueryBuilderV2.styles.scss'; import './QueryBuilderV2.styles.scss';
import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { Formula } from 'container/QueryBuilder/components/Formula';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { memo, useMemo } from 'react'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo, useEffect, useMemo } from 'react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { LogsQB } from './Logs/LogsQB';
import MetricsQB from './Metrics/MetricsQB';
import { QueryBuilderV2Provider } from './QueryBuilderV2Context'; import { QueryBuilderV2Provider } from './QueryBuilderV2Context';
import TracesQB from './Traces/TracesQB'; import QueryFooter from './QueryV2/QueryFooter/QueryFooter';
import { QueryV2 } from './QueryV2/QueryV2';
export type QueryBuilderV2Props = { export const QueryBuilderV2 = memo(function QueryBuilderV2({
source: DataSource; config,
panelType: PANEL_TYPES; panelType: newPanelType,
filterConfigs: QueryBuilderProps['filterConfigs']; filterConfigs = {},
isListViewPanel: boolean; queryComponents,
version: string; isListViewPanel = false,
}; showFunctions = false,
const QueryBuilderV2Main = memo(function QueryBuilderV2Main({
source,
panelType,
filterConfigs,
isListViewPanel,
version, version,
}: QueryBuilderV2Props): JSX.Element { }: QueryBuilderProps): JSX.Element {
const isMetricsDataSource = source === DataSource.METRICS; const {
const isLogsDataSource = source === DataSource.LOGS; currentQuery,
const isTracesDataSource = source === DataSource.TRACES; addNewBuilderQuery,
addNewFormula,
handleSetConfig,
panelType,
initialDataSource,
} = useQueryBuilder();
const listViewLogFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { console.log('isListViewPanel', isListViewPanel, showFunctions);
const config: QueryBuilderProps['filterConfigs'] = {
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
return config; const currentDataSource = useMemo(
}, []); () =>
(config && config.queryVariant === 'static' && config.initialDataSource) ||
const listViewTracesFilterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { null,
const config: QueryBuilderProps['filterConfigs'] = { [config],
stepInterval: { isHidden: true, isDisabled: true },
having: { isHidden: true, isDisabled: true },
limit: { isHidden: true, isDisabled: true },
filters: {
customKey: 'body',
customOp: OPERATORS.CONTAINS,
},
};
return config;
}, []);
return (
<div className="query-builder-v2">
{isMetricsDataSource ? (
<MetricsQB
source={DataSource.METRICS}
filterConfigs={filterConfigs}
panelType={panelType}
version={version}
isListViewPanel={isListViewPanel}
/>
) : null}
{isLogsDataSource ? (
<LogsQB
source={DataSource.LOGS}
filterConfigs={listViewLogFilterConfigs}
panelType={panelType}
version={version}
isListViewPanel={isListViewPanel}
/>
) : null}
{isTracesDataSource ? (
<TracesQB
source={DataSource.TRACES}
filterConfigs={listViewTracesFilterConfigs}
panelType={panelType}
version={version}
isListViewPanel={isListViewPanel}
/>
) : null}
</div>
); );
});
function QueryBuilderV2(props: QueryBuilderV2Props): JSX.Element { useEffect(() => {
const { source, panelType, filterConfigs, isListViewPanel, version } = props; if (currentDataSource !== initialDataSource || newPanelType !== panelType) {
if (newPanelType === PANEL_TYPES.BAR) {
handleSetConfig(PANEL_TYPES.BAR, DataSource.METRICS);
return;
}
handleSetConfig(newPanelType, currentDataSource);
}
}, [
handleSetConfig,
panelType,
initialDataSource,
currentDataSource,
newPanelType,
]);
return ( return (
<QueryBuilderV2Provider> <QueryBuilderV2Provider>
<QueryBuilderV2Main <div className="query-builder-v2">
source={source} <div className="qb-content-container">
panelType={panelType} {currentQuery.builder.queryData.map((query, index) => (
filterConfigs={filterConfigs} <QueryV2
isListViewPanel={isListViewPanel} key={query.queryName}
version={version} index={index}
/> query={query}
filterConfigs={filterConfigs}
queryComponents={queryComponents}
version={version}
isAvailableToDisable={false}
showSpanScopeSelector
queryVariant={config?.queryVariant || 'dropdown'}
/>
))}
{currentQuery.builder.queryFormulas.length > 0 && (
<div className="qb-formulas-container">
{currentQuery.builder.queryFormulas.map((formula, index) => {
const query =
currentQuery.builder.queryData[index] ||
currentQuery.builder.queryData[0];
return (
<div key={formula.queryName} className="qb-formula">
<Formula
filterConfigs={filterConfigs}
query={query}
formula={formula}
index={index}
isAdditionalFilterEnable={false}
/>
</div>
);
})}
</div>
)}
<QueryFooter
addNewBuilderQuery={addNewBuilderQuery}
addNewFormula={addNewFormula}
/>
</div>
<div className="query-names-section">
{currentQuery.builder.queryData.map((query) => (
<div key={query.queryName} className="query-name">
{query.queryName}
</div>
))}
{currentQuery.builder.queryFormulas.map((formula) => (
<div key={formula.queryName} className="formula-name">
{formula.queryName}
</div>
))}
</div>
</div>
</QueryBuilderV2Provider> </QueryBuilderV2Provider>
); );
} });
export default QueryBuilderV2;

View File

@ -17,16 +17,20 @@ import QuerySearch from './QuerySearch/QuerySearch';
export const QueryV2 = memo(function QueryV2({ export const QueryV2 = memo(function QueryV2({
index, index,
queryVariant,
query, query,
filterConfigs, filterConfigs,
queryComponents,
isListViewPanel = false, isListViewPanel = false,
version, version,
showSpanScopeSelector = false, showSpanScopeSelector = false,
source, }: QueryProps): JSX.Element {
}: QueryProps & { source: DataSource }): JSX.Element {
const { cloneQuery } = useQueryBuilder(); const { cloneQuery } = useQueryBuilder();
const showFunctions = query?.functions?.length > 0; const showFunctions = query?.functions?.length > 0;
const { dataSource } = query;
console.log('queryComponents', queryComponents);
const [isCollapsed, setIsCollapsed] = useState(false); const [isCollapsed, setIsCollapsed] = useState(false);
@ -34,6 +38,7 @@ export const QueryV2 = memo(function QueryV2({
handleChangeQueryData, handleChangeQueryData,
handleDeleteQuery, handleDeleteQuery,
handleQueryFunctionsUpdates, handleQueryFunctionsUpdates,
handleChangeDataSource,
} = useQueryOperations({ } = useQueryOperations({
index, index,
query, query,
@ -61,7 +66,7 @@ export const QueryV2 = memo(function QueryV2({
<div className="query-actions-container"> <div className="query-actions-container">
<div className="query-actions-left-container"> <div className="query-actions-left-container">
<QBEntityOptions <QBEntityOptions
isMetricsDataSource={source === DataSource.METRICS} isMetricsDataSource={dataSource === DataSource.METRICS}
showFunctions={ showFunctions={
(version && version === ENTITY_VERSION_V4) || (version && version === ENTITY_VERSION_V4) ||
query.dataSource === DataSource.LOGS || query.dataSource === DataSource.LOGS ||
@ -81,6 +86,8 @@ export const QueryV2 = memo(function QueryV2({
showCloneOption={false} showCloneOption={false}
isListViewPanel={isListViewPanel} isListViewPanel={isListViewPanel}
index={index} index={index}
queryVariant={queryVariant}
onChangeDataSource={handleChangeDataSource}
/> />
</div> </div>
@ -111,7 +118,7 @@ export const QueryV2 = memo(function QueryV2({
<div className="qb-elements-container"> <div className="qb-elements-container">
<div className="qb-search-container"> <div className="qb-search-container">
{source === DataSource.METRICS && ( {dataSource === DataSource.METRICS && (
<div className="metrics-select-container"> <div className="metrics-select-container">
<MetricsSelect query={query} index={0} version="v4" /> <MetricsSelect query={query} index={0} version="v4" />
</div> </div>
@ -129,9 +136,9 @@ export const QueryV2 = memo(function QueryV2({
</div> </div>
</div> </div>
<QueryAggregation source={source} /> <QueryAggregation source={dataSource} />
{source === DataSource.METRICS && ( {dataSource === DataSource.METRICS && (
<MetricsAggregateSection query={query} index={0} version="v4" /> <MetricsAggregateSection query={query} index={0} version="v4" />
)} )}

View File

@ -1,5 +0,0 @@
.traces-qb {
display: flex;
flex-direction: row;
gap: 8px;
}

View File

@ -1,88 +0,0 @@
import './TracesQB.styles.scss';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { Formula } from 'container/QueryBuilder/components/Formula';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { DataSource } from 'types/common/queryBuilder';
import { QueryBuilderV2Props } from '../QueryBuilderV2';
import QueryFooter from '../QueryV2/QueryFooter/QueryFooter';
import { QueryV2 } from '../QueryV2/QueryV2';
function TracesQB({ filterConfigs }: QueryBuilderV2Props): JSX.Element {
const version = ENTITY_VERSION_V4;
const { currentQuery, addNewFormula, addNewBuilderQuery } = useQueryBuilder();
const { isMetricsDataSource } = useQueryOperations({
index: 0,
query: currentQuery.builder.queryData[0],
filterConfigs,
isListViewPanel: false,
entityVersion: version,
});
return (
<div className="traces-qb">
<div className="qb-content-container">
{currentQuery.builder.queryData.map((query, index) => (
<QueryV2
key={query.queryName}
index={index}
query={query}
filterConfigs={filterConfigs}
version={version}
isAvailableToDisable={false}
queryVariant="static"
source={DataSource.TRACES}
showSpanScopeSelector
/>
))}
{currentQuery.builder.queryFormulas.length > 0 && (
<div className="qb-formulas-container">
{currentQuery.builder.queryFormulas.map((formula, index) => {
const query =
currentQuery.builder.queryData[index] ||
currentQuery.builder.queryData[0];
return (
<div key={formula.queryName} className="qb-formula">
<Formula
filterConfigs={filterConfigs}
query={query}
formula={formula}
index={index}
isAdditionalFilterEnable={isMetricsDataSource}
/>
</div>
);
})}
</div>
)}
<QueryFooter
addNewBuilderQuery={addNewBuilderQuery}
addNewFormula={addNewFormula}
/>
</div>
<div className="query-names-section">
{currentQuery.builder.queryData.map((query) => (
<div key={query.queryName} className="query-name">
{query.queryName}
</div>
))}
{currentQuery.builder.queryFormulas.map((formula) => (
<div key={formula.queryName} className="formula-name">
{formula.queryName}
</div>
))}
</div>
</div>
);
}
export default TracesQB;

View File

@ -1,13 +1,12 @@
import './LogsExplorerQuerySection.styles.scss'; import './LogsExplorerQuerySection.styles.scss';
import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2'; import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import { import {
initialQueriesMap, initialQueriesMap,
OPERATORS, OPERATORS,
PANEL_TYPES, PANEL_TYPES,
} from 'constants/queryBuilder'; } from 'constants/queryBuilder';
import ExplorerOrderBy from 'container/ExplorerOrderBy'; import ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2'; import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
@ -16,8 +15,8 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { import {
ExplorerViews,
prepareQueryWithDefaultTimestamp, prepareQueryWithDefaultTimestamp,
SELECTED_VIEWS,
} from 'pages/LogsExplorer/utils'; } from 'pages/LogsExplorer/utils';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
@ -26,7 +25,7 @@ import { DataSource } from 'types/common/queryBuilder';
function LogExplorerQuerySection({ function LogExplorerQuerySection({
selectedView, selectedView,
}: { }: {
selectedView: SELECTED_VIEWS; selectedView: ExplorerViews;
}): JSX.Element { }): JSX.Element {
const { currentQuery, updateAllQueriesOperators } = useQueryBuilder(); const { currentQuery, updateAllQueriesOperators } = useQueryBuilder();
@ -89,7 +88,7 @@ function LogExplorerQuerySection({
return ( return (
<> <>
{selectedView === SELECTED_VIEWS.SEARCH && ( {selectedView === ExplorerViews.LIST && (
<div className="qb-search-view-container"> <div className="qb-search-view-container">
<QueryBuilderSearchV2 <QueryBuilderSearchV2
query={query} query={query}
@ -99,25 +98,18 @@ function LogExplorerQuerySection({
</div> </div>
)} )}
{selectedView === SELECTED_VIEWS.QUERY_BUILDER && ( {(selectedView === ExplorerViews.TABLE ||
<QueryBuilder selectedView === ExplorerViews.TIMESERIES ||
panelType={panelTypes} selectedView === ExplorerViews.CLICKHOUSE) && (
<QueryBuilderV2
isListViewPanel={panelTypes === PANEL_TYPES.LIST}
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }} config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
panelType={panelTypes}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
queryComponents={queryComponents} queryComponents={queryComponents}
version="v3" // setting this to v3 as we this is rendered in logs explorer version="v3" // setting this to v3 as we this is rendered in logs explorer
/> />
)} )}
{selectedView === SELECTED_VIEWS.QUERY_BUILDER_V2 && (
<QueryBuilderV2
source={DataSource.LOGS}
isListViewPanel={panelTypes === PANEL_TYPES.LIST}
panelType={panelTypes}
filterConfigs={filterConfigs}
version="v3" // setting this to v3 as we this is rendered in logs explorer
/>
)}
</> </>
); );
} }

View File

@ -80,10 +80,54 @@
position: relative; position: relative;
} }
} }
}
.logs-actions-container {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
height: 40px;
padding: 4px 8px;
width: 100%;
border-top: 1px solid var(--Slate-500, #161922);
border-bottom: 1px solid var(--Slate-500, #161922);
box-shadow: 0px 8px 6px 0px #0b0c0e;
.tab-options {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
.tab-options-left {
display: flex;
align-items: center;
gap: 8px;
}
.tab-options-right {
display: flex;
align-items: center;
gap: 8px;
}
.frequency-chart-view-controller {
display: flex;
align-items: center;
gap: 8px;
}
}
.query-stats { .query-stats {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
align-self: flex-end;
.rows { .rows {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
font-family: 'Geist Mono'; font-family: 'Geist Mono';
@ -110,13 +154,6 @@
letter-spacing: 0.36px; letter-spacing: 0.36px;
} }
} }
}
.logs-actions-container {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.ant-btn { .ant-btn {
border: none; border: none;
@ -186,6 +223,14 @@
background: var(--bg-robin-400); background: var(--bg-robin-400);
} }
} }
}
.logs-actions-container {
.tab-options {
border-top: 1px solid var(--text-vanilla-300);
border-bottom: 1px solid var(--text-vanilla-300);
}
.query-stats { .query-stats {
.rows { .rows {
color: var(--bg-ink-400); color: var(--bg-ink-400);

View File

@ -1,7 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */ /* eslint-disable sonarjs/cognitive-complexity */
import './LogsExplorerViews.styles.scss'; import './LogsExplorerViews.styles.scss';
import { Button, Typography } from 'antd'; import { Button, Switch, Typography } from 'antd';
import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats'; import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig'; import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
@ -47,7 +47,7 @@ import {
set, set,
} from 'lodash-es'; } from 'lodash-es';
import { Sliders } from 'lucide-react'; import { Sliders } from 'lucide-react';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { ExplorerViews } from 'pages/LogsExplorer/utils';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { import {
memo, memo,
@ -80,15 +80,13 @@ import { v4 } from 'uuid';
import QueryStatus from './QueryStatus'; import QueryStatus from './QueryStatus';
function LogsExplorerViews({ function LogsExplorerViewsContainer({
selectedView, selectedView,
showFrequencyChart,
setIsLoadingQueries, setIsLoadingQueries,
listQueryKeyRef, listQueryKeyRef,
chartQueryKeyRef, chartQueryKeyRef,
}: { }: {
selectedView: SELECTED_VIEWS; selectedView: ExplorerViews;
showFrequencyChart: boolean;
setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>; setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
listQueryKeyRef: MutableRefObject<any>; listQueryKeyRef: MutableRefObject<any>;
@ -97,6 +95,7 @@ function LogsExplorerViews({
}): JSX.Element { }): JSX.Element {
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [showFrequencyChart, setShowFrequencyChart] = useState(true);
// this is to respect the panel type present in the URL rather than defaulting it to list always. // this is to respect the panel type present in the URL rather than defaulting it to list always.
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
@ -240,15 +239,6 @@ function LogsExplorerViews({
[currentQuery, selectedPanelType, updateAllQueriesOperators], [currentQuery, selectedPanelType, updateAllQueriesOperators],
); );
const handleModeChange = (panelType: PANEL_TYPES): void => {
if (selectedView === SELECTED_VIEWS.SEARCH) {
handleSetConfig(panelType, DataSource.LOGS);
}
setShowFormatMenuItems(false);
handleExplorerTabChange(panelType);
};
const { const {
data: listChartData, data: listChartData,
isFetching: isFetchingListChartData, isFetching: isFetchingListChartData,
@ -460,8 +450,7 @@ function LogsExplorerViews({
useEffect(() => { useEffect(() => {
const shouldChangeView = const shouldChangeView =
(isMultipleQueries || isGroupByExist) && (isMultipleQueries || isGroupByExist) && selectedView !== ExplorerViews.LIST;
selectedView !== SELECTED_VIEWS.SEARCH;
if (selectedPanelType === PANEL_TYPES.LIST && shouldChangeView) { if (selectedPanelType === PANEL_TYPES.LIST && shouldChangeView) {
handleExplorerTabChange(PANEL_TYPES.TIME_SERIES); handleExplorerTabChange(PANEL_TYPES.TIME_SERIES);
@ -481,11 +470,7 @@ function LogsExplorerViews({
]); ]);
useEffect(() => { useEffect(() => {
if ( if (selectedView && selectedView === ExplorerViews.LIST && handleSetConfig) {
selectedView &&
selectedView === SELECTED_VIEWS.SEARCH &&
handleSetConfig
) {
handleSetConfig(defaultTo(panelTypes, PANEL_TYPES.LIST), DataSource.LOGS); handleSetConfig(defaultTo(panelTypes, PANEL_TYPES.LIST), DataSource.LOGS);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -651,111 +636,90 @@ function LogsExplorerViews({
return ( return (
<div className="logs-explorer-views-container"> <div className="logs-explorer-views-container">
{showFrequencyChart && (
<LogsExplorerChart
className="logs-histogram"
isLoading={isFetchingListChartData || isLoadingListChartData}
data={chartData}
isLogsExplorerViews={panelType === PANEL_TYPES.LIST}
/>
)}
<div className="logs-explorer-views-types"> <div className="logs-explorer-views-types">
<div className="views-tabs-container"> <div className="logs-actions-container">
<Button.Group className="views-tabs"> <div className="tab-options">
<Button <div className="tab-options-left">
value={PANEL_TYPES.LIST} {selectedPanelType === PANEL_TYPES.LIST && (
className={ <div className="frequency-chart-view-controller">
// eslint-disable-next-line sonarjs/no-duplicate-string <Typography>Frequency chart</Typography>
selectedPanelType === PANEL_TYPES.LIST ? 'selected_view tab' : 'tab' <Switch
} size="small"
disabled={ checked={showFrequencyChart}
(isMultipleQueries || isGroupByExist) && selectedView !== 'search' defaultChecked
} onChange={(): void => setShowFrequencyChart(!showFrequencyChart)}
onClick={(): void => handleModeChange(PANEL_TYPES.LIST)} />
data-testid="logs-list-view" </div>
> )}
List view </div>
</Button>
<Button <div className="tab-options-right">
value={PANEL_TYPES.TIME_SERIES} {selectedPanelType === PANEL_TYPES.LIST && (
className={ <>
// eslint-disable-next-line sonarjs/no-duplicate-string <Download
selectedPanelType === PANEL_TYPES.TIME_SERIES data={flattenLogData}
? 'selected_view tab' isLoading={isFetching}
: 'tab' fileName="log_data"
} />
onClick={(): void => handleModeChange(PANEL_TYPES.TIME_SERIES)} <div className="format-options-container" ref={menuRef}>
data-testid="time-series-view" <Button
> className="periscope-btn"
Time series onClick={handleToggleShowFormatOptions}
</Button> icon={<Sliders size={14} />}
<Button data-testid="periscope-btn"
value={PANEL_TYPES.TABLE} />
className={
// eslint-disable-next-line sonarjs/no-duplicate-string {showFormatMenuItems && (
selectedPanelType === PANEL_TYPES.TABLE ? 'selected_view tab' : 'tab' <LogsFormatOptionsMenu
} title="FORMAT"
onClick={(): void => handleModeChange(PANEL_TYPES.TABLE)} items={formatItems}
data-testid="table-view" selectedOptionFormat={options.format}
> config={config}
Table />
</Button> )}
</Button.Group> </div>
<div className="logs-actions-container"> </>
{selectedPanelType === PANEL_TYPES.LIST && ( )}
<div className="tab-options">
<Download {(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
data={flattenLogData} selectedPanelType === PANEL_TYPES.TABLE) && (
isLoading={isFetching} <div className="query-stats">
fileName="log_data" <QueryStatus
/> loading={isLoading || isFetching}
<div className="format-options-container" ref={menuRef}> error={isError}
<Button success={isSuccess}
className="periscope-btn"
onClick={handleToggleShowFormatOptions}
icon={<Sliders size={14} />}
data-testid="periscope-btn"
/> />
{showFormatMenuItems && ( {queryStats?.read_rows && (
<LogsFormatOptionsMenu <Typography.Text className="rows">
title="FORMAT" {getYAxisFormattedValue(queryStats.read_rows?.toString(), 'short')}{' '}
items={formatItems} rows
selectedOptionFormat={options.format} </Typography.Text>
config={config} )}
/>
{queryStats?.elapsed_ms && (
<>
<div className="divider" />
<Typography.Text className="time">
{getYAxisFormattedValue(queryStats?.elapsed_ms?.toString(), 'ms')}
</Typography.Text>
</>
)} )}
</div> </div>
</div> )}
)} </div>
{(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
selectedPanelType === PANEL_TYPES.TABLE) && (
<div className="query-stats">
<QueryStatus
loading={isLoading || isFetching}
error={isError}
success={isSuccess}
/>
{queryStats?.read_rows && (
<Typography.Text className="rows">
{getYAxisFormattedValue(queryStats.read_rows?.toString(), 'short')}{' '}
rows
</Typography.Text>
)}
{queryStats?.elapsed_ms && (
<>
<div className="divider" />
<Typography.Text className="time">
{getYAxisFormattedValue(queryStats?.elapsed_ms?.toString(), 'ms')}
</Typography.Text>
</>
)}
</div>
)}
</div> </div>
</div> </div>
{selectedPanelType === PANEL_TYPES.LIST && showFrequencyChart && (
<LogsExplorerChart
className="logs-histogram"
isLoading={isFetchingListChartData || isLoadingListChartData}
data={chartData}
isLogsExplorerViews={panelType === PANEL_TYPES.LIST}
/>
)}
<div className="logs-explorer-views-type-content"> <div className="logs-explorer-views-type-content">
{selectedPanelType === PANEL_TYPES.LIST && ( {selectedPanelType === PANEL_TYPES.LIST && (
<LogsExplorerList <LogsExplorerList
@ -801,4 +765,4 @@ function LogsExplorerViews({
); );
} }
export default memo(LogsExplorerViews); export default memo(LogsExplorerViewsContainer);

View File

@ -4,7 +4,7 @@ import { useGetExplorerQueryRange } from 'hooks/queryBuilder/useGetExplorerQuery
import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range'; import { logsQueryRangeSuccessResponse } from 'mocks-server/__mockdata__/logs_query_range';
import { server } from 'mocks-server/server'; import { server } from 'mocks-server/server';
import { rest } from 'msw'; import { rest } from 'msw';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { ExplorerViews } from 'pages/LogsExplorer/utils';
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider'; import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
import { QueryBuilderContext } from 'providers/QueryBuilder'; import { QueryBuilderContext } from 'providers/QueryBuilder';
import { VirtuosoMockContext } from 'react-virtuoso'; import { VirtuosoMockContext } from 'react-virtuoso';
@ -127,8 +127,7 @@ const renderer = (): RenderResult =>
> >
<PreferenceContextProvider> <PreferenceContextProvider>
<LogsExplorerViews <LogsExplorerViews
selectedView={SELECTED_VIEWS.SEARCH} selectedView={ExplorerViews.LIST}
showFrequencyChart
setIsLoadingQueries={(): void => {}} setIsLoadingQueries={(): void => {}}
listQueryKeyRef={{ current: {} }} listQueryKeyRef={{ current: {} }}
chartQueryKeyRef={{ current: {} }} chartQueryKeyRef={{ current: {} }}
@ -212,8 +211,7 @@ describe('LogsExplorerViews -', () => {
<QueryBuilderContext.Provider value={mockQueryBuilderContextValue}> <QueryBuilderContext.Provider value={mockQueryBuilderContextValue}>
<PreferenceContextProvider> <PreferenceContextProvider>
<LogsExplorerViews <LogsExplorerViews
selectedView={SELECTED_VIEWS.SEARCH} selectedView={ExplorerViews.LIST}
showFrequencyChart
setIsLoadingQueries={(): void => {}} setIsLoadingQueries={(): void => {}}
listQueryKeyRef={{ current: {} }} listQueryKeyRef={{ current: {} }}
chartQueryKeyRef={{ current: {} }} chartQueryKeyRef={{ current: {} }}

View File

@ -5,7 +5,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin: 10px 0; margin: 4px 0;
padding: 0 1rem; padding: 0 1rem;
.explore-header-left-actions { .explore-header-left-actions {
@ -56,7 +56,7 @@
} }
.explore-content { .explore-content {
margin-top: 10px; padding: 0 8px;
.ant-space { .ant-space {
margin-top: 10px; margin-top: 10px;

View File

@ -3,10 +3,11 @@ import './Explorer.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Switch } from 'antd'; import { Switch } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2'; import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper'; import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions'; import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2'; import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl'; import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
@ -102,6 +103,11 @@ function Explorer(): JSX.Element {
}); });
}, []); }, []);
const queryComponents = useMemo(
(): QueryBuilderProps['queryComponents'] => ({}),
[],
);
return ( return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}> <Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="metrics-explorer-explore-container"> <div className="metrics-explorer-explore-container">
@ -121,8 +127,12 @@ function Explorer(): JSX.Element {
</div> </div>
{/* <QuerySection /> */} {/* <QuerySection /> */}
<QueryBuilderV2 <QueryBuilderV2
source={DataSource.METRICS} config={{ initialDataSource: DataSource.METRICS, queryVariant: 'static' }}
query={currentQuery.builder.queryData[0]} panelType={PANEL_TYPES.TIME_SERIES}
queryComponents={queryComponents}
showFunctions={false}
version="v3"
isListViewPanel
/> />
{/* TODO: Enable once we have resolved all related metrics issues */} {/* TODO: Enable once we have resolved all related metrics issues */}
{/* <Button.Group className="explore-tabs"> {/* <Button.Group className="explore-tabs">

View File

@ -1,10 +1,10 @@
import './QuerySection.styles.scss'; import './QuerySection.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Button, Select, Tabs, Typography } from 'antd'; import { Button, Tabs, Typography } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import PromQLIcon from 'assets/Dashboard/PromQl'; import PromQLIcon from 'assets/Dashboard/PromQl';
import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2'; import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import TextToolTip from 'components/TextToolTip'; import TextToolTip from 'components/TextToolTip';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { QBShortcuts } from 'constants/shortcuts/QBShortcuts'; import { QBShortcuts } from 'constants/shortcuts/QBShortcuts';
@ -27,14 +27,13 @@ import {
getPreviousWidgets, getPreviousWidgets,
getSelectedWidgetIndex, getSelectedWidgetIndex,
} from 'providers/Dashboard/util'; } from 'providers/Dashboard/util';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { UseQueryResult } from 'react-query'; import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll'; import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import ClickHouseQueryContainer from './QueryBuilder/clickHouse'; import ClickHouseQueryContainer from './QueryBuilder/clickHouse';
import PromQLQueryContainer from './QueryBuilder/promQL'; import PromQLQueryContainer from './QueryBuilder/promQL';
@ -49,10 +48,6 @@ function QuerySection({
const { selectedDashboard, setSelectedDashboard } = useDashboard(); const { selectedDashboard, setSelectedDashboard } = useDashboard();
const [selectedDataSource, setSelectedDataSource] = useState<DataSource>(
DataSource.METRICS,
);
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const { widgets } = selectedDashboard?.data || {}; const { widgets } = selectedDashboard?.data || {};
@ -148,6 +143,11 @@ function QuerySection({
return config; return config;
}, []); }, []);
const queryComponents = useMemo(
(): QueryBuilderProps['queryComponents'] => ({}),
[],
);
const items = useMemo(() => { const items = useMemo(() => {
const supportedQueryTypes = PANEL_TYPE_TO_QUERY_TYPES[selectedGraph] || []; const supportedQueryTypes = PANEL_TYPE_TO_QUERY_TYPES[selectedGraph] || [];
@ -157,20 +157,12 @@ function QuerySection({
label: 'Query Builder', label: 'Query Builder',
component: ( component: (
<div className="query-builder-v2-container"> <div className="query-builder-v2-container">
<Select
onChange={setSelectedDataSource}
value={selectedDataSource}
options={Object.values(DataSource).map((dataSource) => ({
label: dataSource,
value: dataSource,
}))}
/>
<QueryBuilderV2 <QueryBuilderV2
panelType={selectedGraph} panelType={selectedGraph}
filterConfigs={filterConfigs} filterConfigs={filterConfigs}
version={selectedDashboard?.data?.version || 'v3'} version={selectedDashboard?.data?.version || 'v3'}
isListViewPanel={selectedGraph === PANEL_TYPES.LIST} isListViewPanel={selectedGraph === PANEL_TYPES.LIST}
source={selectedDataSource} queryComponents={queryComponents}
/> />
</div> </div>
), ),
@ -203,11 +195,11 @@ function QuerySection({
children: queryTypeComponents[queryType].component, children: queryTypeComponents[queryType].component,
})); }));
}, [ }, [
queryComponents,
selectedGraph, selectedGraph,
filterConfigs, filterConfigs,
selectedDashboard?.data?.version, selectedDashboard?.data?.version,
isDarkMode, isDarkMode,
selectedDataSource,
]); ]);
useEffect(() => { useEffect(() => {

View File

@ -22,6 +22,7 @@ import {
} from 'types/api/queryBuilder/queryBuilderData'; } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { DataSourceDropdown } from '..';
import QueryFunctions from '../QueryFunctions/QueryFunctions'; import QueryFunctions from '../QueryFunctions/QueryFunctions';
interface QBEntityOptionsProps { interface QBEntityOptionsProps {
@ -40,8 +41,11 @@ interface QBEntityOptionsProps {
showCloneOption?: boolean; showCloneOption?: boolean;
isListViewPanel?: boolean; isListViewPanel?: boolean;
index?: number; index?: number;
queryVariant?: 'dropdown' | 'static';
onChangeDataSource?: (value: DataSource) => void;
} }
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function QBEntityOptions({ export default function QBEntityOptions({
query, query,
isMetricsDataSource, isMetricsDataSource,
@ -58,6 +62,8 @@ export default function QBEntityOptions({
showCloneOption, showCloneOption,
onCloneQuery, onCloneQuery,
index, index,
queryVariant,
onChangeDataSource,
}: QBEntityOptionsProps): JSX.Element { }: QBEntityOptionsProps): JSX.Element {
const handleCloneEntity = (): void => { const handleCloneEntity = (): void => {
if (isFunction(onCloneQuery)) { if (isFunction(onCloneQuery)) {
@ -117,6 +123,21 @@ export default function QBEntityOptions({
{entityData.queryName} {entityData.queryName}
</Button> </Button>
{queryVariant === 'dropdown' && (
<div className="query-data-source">
<DataSourceDropdown
onChange={(value): void => {
if (onChangeDataSource) {
onChangeDataSource(value);
}
}}
value={query?.dataSource || DataSource.METRICS}
isListViewPanel={isListViewPanel}
className="query-data-source-dropdown"
/>
</div>
)}
{showFunctions && {showFunctions &&
(isMetricsDataSource || isLogsDataSource) && (isMetricsDataSource || isLogsDataSource) &&
query && query &&
@ -161,4 +182,6 @@ QBEntityOptions.defaultProps = {
onDelete: noop, onDelete: noop,
showDeleteButton: false, showDeleteButton: false,
showCloneOption: true, showCloneOption: true,
queryVariant: 'static',
onChangeDataSource: noop,
}; };

View File

@ -350,6 +350,7 @@ export const Query = memo(function Query({
showDeleteButton={currentQuery.builder.queryData.length > 1} showDeleteButton={currentQuery.builder.queryData.length > 1}
isListViewPanel={isListViewPanel} isListViewPanel={isListViewPanel}
index={index} index={index}
queryVariant={queryVariant}
/> />
{!isCollapse && ( {!isCollapse && (

View File

@ -1,35 +1,31 @@
/* eslint-disable sonarjs/no-duplicate-string */
import './ToolbarActions.styles.scss'; import './ToolbarActions.styles.scss';
import { FilterOutlined } from '@ant-design/icons'; import { FilterOutlined } from '@ant-design/icons';
import { Button, Switch, Tooltip, Typography } from 'antd'; import { Button, Tooltip } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { Atom, Binoculars, SquareMousePointer, Terminal } from 'lucide-react'; import { Atom, Binoculars, SquareMousePointer, Terminal } from 'lucide-react';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { ExplorerViews } from 'pages/LogsExplorer/utils';
interface LeftToolbarActionsProps { interface LeftToolbarActionsProps {
items: any; items: any;
selectedView: string; selectedView: string;
onToggleHistrogramVisibility: () => void; onChangeSelectedView: (view: ExplorerViews) => void;
onChangeSelectedView: (view: SELECTED_VIEWS) => void;
showFrequencyChart: boolean;
showFilter: boolean; showFilter: boolean;
handleFilterVisibilityChange: () => void; handleFilterVisibilityChange: () => void;
} }
const activeTab = 'active-tab'; const activeTab = 'active-tab';
const actionBtn = 'action-btn';
export const queryBuilder = 'query-builder';
export const queryBuilderV2 = 'query-builder-v2';
export default function LeftToolbarActions({ export default function LeftToolbarActions({
items, items,
selectedView, selectedView,
onToggleHistrogramVisibility,
onChangeSelectedView, onChangeSelectedView,
showFrequencyChart,
showFilter, showFilter,
handleFilterVisibilityChange, handleFilterVisibilityChange,
}: LeftToolbarActionsProps): JSX.Element { }: LeftToolbarActionsProps): JSX.Element {
const { clickhouse, search, queryBuilder: QB } = items; const { clickhouse, list, timeseries, table, trace } = items;
return ( return (
<div className="left-toolbar"> <div className="left-toolbar">
@ -41,72 +37,90 @@ export default function LeftToolbarActions({
</Tooltip> </Tooltip>
)} )}
<div className="left-toolbar-query-actions"> <div className="left-toolbar-query-actions">
<Tooltip title="Search"> {list?.show && (
<Button <Tooltip title="List View">
disabled={search.disabled} <Button
className={cx( disabled={list.disabled}
'search', className={cx(
actionBtn, 'list-view-tab',
selectedView === 'search' ? activeTab : '', 'explorer-view-option',
)} selectedView === list.key ? activeTab : '',
onClick={(): void => onChangeSelectedView(SELECTED_VIEWS.SEARCH)} )}
> onClick={(): void => onChangeSelectedView(list.key)}
<SquareMousePointer size={14} data-testid="search-view" /> >
</Button> <SquareMousePointer size={14} data-testid="search-view" />
</Tooltip> List View
<Tooltip title="Query Builder"> </Button>
<Button </Tooltip>
disabled={QB.disabled}
className={cx(
queryBuilder,
actionBtn,
selectedView === queryBuilder ? activeTab : '',
)}
onClick={(): void => onChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER)}
>
<Atom size={14} data-testid="query-builder-view" />
</Button>
</Tooltip>
{clickhouse?.show && (
<Button
disabled={clickhouse.disabled}
className={cx(
SELECTED_VIEWS.CLICKHOUSE,
actionBtn,
selectedView === SELECTED_VIEWS.CLICKHOUSE ? activeTab : '',
)}
onClick={(): void => onChangeSelectedView(SELECTED_VIEWS.CLICKHOUSE)}
>
<Terminal size={14} data-testid="clickhouse-view" />
</Button>
)} )}
<Tooltip title="Query Builder V2"> {trace?.show && (
<Button <Tooltip title="Trace View">
disabled={QB.disabled} <Button
className={cx( disabled={trace.disabled}
queryBuilderV2, className={cx(
actionBtn, 'trace-view-tab',
selectedView === queryBuilderV2 ? activeTab : '', 'explorer-view-option',
)} selectedView === trace.key ? activeTab : '',
onClick={(): void => )}
onChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER_V2) onClick={(): void => onChangeSelectedView(trace.key)}
} >
> <SquareMousePointer size={14} data-testid="trace-view" />
<Binoculars size={14} data-testid="query-builder-view-v2" /> Trace View
</Button> </Button>
</Tooltip> </Tooltip>
</div> )}
<div className="frequency-chart-view-controller"> {timeseries?.show && (
<Typography>Frequency chart</Typography> <Tooltip title="Time Series">
<Switch <Button
size="small" disabled={timeseries.disabled}
checked={showFrequencyChart} className={cx(
defaultChecked 'timeseries-view-tab',
onChange={onToggleHistrogramVisibility} 'explorer-view-option',
/> selectedView === timeseries.key ? activeTab : '',
)}
onClick={(): void => onChangeSelectedView(timeseries.key)}
>
<Atom size={14} data-testid="query-builder-view" />
Time Series
</Button>
</Tooltip>
)}
{clickhouse?.show && (
<Tooltip title="Clickhouse">
<Button
disabled={clickhouse.disabled}
className={cx(
'clickhouse-view-tab',
'explorer-view-option',
selectedView === clickhouse.key ? activeTab : '',
)}
onClick={(): void => onChangeSelectedView(clickhouse.key)}
>
<Terminal size={14} data-testid="clickhouse-view" />
Clickhouse
</Button>
</Tooltip>
)}
{table?.show && (
<Tooltip title="Table">
<Button
disabled={table.disabled}
className={cx(
'table-view-tab',
'explorer-view-option',
selectedView === table.key ? activeTab : '',
)}
onClick={(): void => onChangeSelectedView(table.key)}
>
<Binoculars size={14} data-testid="query-builder-view-v2" />
Table
</Button>
</Tooltip>
)}
</div> </div>
</div> </div>
); );

View File

@ -25,14 +25,16 @@
width: 14px; width: 14px;
} }
.ant-btn { .explorer-view-option {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-direction: row;
border: none; border: none;
padding: 9px; padding: 9px;
box-shadow: none; box-shadow: none;
border-radius: 0; border-radius: 0;
gap: 8px;
&.active-tab { &.active-tab {
background-color: var(--bg-slate-400); background-color: var(--bg-slate-400);
@ -43,9 +45,6 @@
opacity: 0.6; opacity: 0.6;
} }
} }
.action-btn + .action-btn {
border-left: 1px solid var(--bg-slate-400);
}
} }
.frequency-chart-view-controller { .frequency-chart-view-controller {

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils'; import { ExplorerViews } from 'pages/LogsExplorer/utils';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider'; import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import LeftToolbarActions from '../LeftToolbarActions'; import LeftToolbarActions from '../LeftToolbarActions';
@ -9,7 +9,6 @@ import RightToolbarActions from '../RightToolbarActions';
describe('ToolbarActions', () => { describe('ToolbarActions', () => {
it('LeftToolbarActions - renders correctly with default props', async () => { it('LeftToolbarActions - renders correctly with default props', async () => {
const handleChangeSelectedView = jest.fn(); const handleChangeSelectedView = jest.fn();
const handleToggleShowFrequencyChart = jest.fn();
const { queryByTestId } = render( const { queryByTestId } = render(
<LeftToolbarActions <LeftToolbarActions
items={{ items={{
@ -31,10 +30,8 @@ describe('ToolbarActions', () => {
disabled: false, disabled: false,
}, },
}} }}
selectedView={SELECTED_VIEWS.SEARCH} selectedView={ExplorerViews.LIST}
onChangeSelectedView={handleChangeSelectedView} onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart
showFilter showFilter
handleFilterVisibilityChange={(): void => {}} handleFilterVisibilityChange={(): void => {}}
/>, />,
@ -77,10 +74,8 @@ describe('ToolbarActions', () => {
show: true, show: true,
}, },
}} }}
selectedView={SELECTED_VIEWS.QUERY_BUILDER} selectedView={ExplorerViews.TIMESERIES}
onChangeSelectedView={handleChangeSelectedView} onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart
showFilter showFilter
handleFilterVisibilityChange={(): void => {}} handleFilterVisibilityChange={(): void => {}}
/>, />,

View File

@ -0,0 +1,11 @@
.time-series-view {
height: 50vh;
min-height: 350px;
padding: 0px 12px;
.ant-card-body {
height: 50vh;
min-height: 350px;
padding: 0px 12px;
}
}

View File

@ -33,8 +33,6 @@ import { GlobalReducer } from 'types/reducer/globalTime';
import uPlot from 'uplot'; import uPlot from 'uplot';
import { getTimeRange } from 'utils/getTimeRange'; import { getTimeRange } from 'utils/getTimeRange';
import { Container } from './styles';
function TimeSeriesView({ function TimeSeriesView({
data, data,
isLoading, isLoading,
@ -162,7 +160,7 @@ function TimeSeriesView({
}); });
return ( return (
<Container> <div className="time-series-view">
{isError && <LogsError />} {isError && <LogsError />}
<div <div
className="graph-container" className="graph-container"
@ -204,7 +202,7 @@ function TimeSeriesView({
!isEmpty(chartData?.[0]) && !isEmpty(chartData?.[0]) &&
chartOptions && <Uplot data={chartData} options={chartOptions} />} chartOptions && <Uplot data={chartData} options={chartOptions} />}
</div> </div>
</Container> </div>
); );
} }

View File

@ -1,3 +1,5 @@
import './TimeSeriesView.styles.scss';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';

View File

@ -0,0 +1,4 @@
.trace-explorer-controls {
display: flex;
justify-content: flex-end;
}

View File

@ -1,3 +1,5 @@
import './ListView.styles.scss';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import { ResizeTable } from 'components/ResizeTable'; import { ResizeTable } from 'components/ResizeTable';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
@ -167,12 +169,14 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
return ( return (
<Container> <Container>
{transformedQueryTableData.length !== 0 && ( {transformedQueryTableData.length !== 0 && (
<TraceExplorerControls <div className="trace-explorer-controls">
isLoading={isFetching} <TraceExplorerControls
totalCount={totalCount} isLoading={isFetching}
config={config} totalCount={totalCount}
perPageOptions={PER_PAGE_OPTIONS} config={config}
/> perPageOptions={PER_PAGE_OPTIONS}
/>
</div>
)} )}
{isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>} {isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>}

View File

@ -1,18 +1,25 @@
import { Button } from 'antd'; import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
// import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOrderBy from 'container/ExplorerOrderBy'; import ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
// import SpanScopeSelector from 'container/QueryBuilder/filters/QueryBuilderSearchV2/SpanScopeSelector';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; // import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { ButtonWrapper, Container } from './styles'; import { Container } from './styles';
function QuerySection(): JSX.Element { function QuerySection({
const { handleRunQuery } = useQueryBuilder(); selectedView,
}: {
selectedView: PANEL_TYPES;
}): JSX.Element {
// const { currentQuery } = useQueryBuilder();
// const queryName = currentQuery?.builder?.queryData[0]?.queryName || '';
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
@ -43,25 +50,30 @@ function QuerySection(): JSX.Element {
}; };
}, [panelTypes, renderOrderBy]); }, [panelTypes, renderOrderBy]);
console.log('query - section - selectedView', selectedView);
return ( return (
<Container> <Container>
<QueryBuilder {/* {(selectedView === 'list' || selectedView === 'trace') && (
panelType={panelTypes} <div className="qb-search-view-container">
config={{ <QuerySearch />
queryVariant: 'static',
initialDataSource: DataSource.TRACES, <div className="traces-search-filter-in">in</div>
}}
filterConfigs={filterConfigs} <SpanScopeSelector queryName={queryName} />
</div>
)}
{(selectedView === 'graph' || selectedView === 'table') && ( */}
<QueryBuilderV2
isListViewPanel={panelTypes === PANEL_TYPES.LIST}
config={{ initialDataSource: DataSource.TRACES, queryVariant: 'static' }}
queryComponents={queryComponents} queryComponents={queryComponents}
panelType={panelTypes}
filterConfigs={filterConfigs}
version="v3" // setting this to v3 as we this is rendered in logs explorer version="v3" // setting this to v3 as we this is rendered in logs explorer
actions={
<ButtonWrapper>
<Button onClick={(): void => handleRunQuery()} type="primary">
Run Query
</Button>
</ButtonWrapper>
}
/> />
{/* )} */}
</Container> </Container>
); );
} }

View File

@ -58,6 +58,8 @@ export const useHandleExplorerTabChange = (): {
currentQueryData?: ICurrentQueryData, currentQueryData?: ICurrentQueryData,
redirectToUrl?: typeof ROUTES[keyof typeof ROUTES], redirectToUrl?: typeof ROUTES[keyof typeof ROUTES],
) => { ) => {
console.log('type', type);
const newPanelType = type as PANEL_TYPES; const newPanelType = type as PANEL_TYPES;
if (newPanelType === panelType && !currentQueryData) return; if (newPanelType === panelType && !currentQueryData) return;

View File

@ -8,8 +8,9 @@ import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
import QuickFilters from 'components/QuickFilters/QuickFilters'; import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types'; import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
import LogExplorerQuerySection from 'container/LogExplorerQuerySection'; import LogExplorerQuerySection from 'container/LogExplorerQuerySection';
import LogsExplorerViews from 'container/LogsExplorerViews'; import LogsExplorerViewsContainer from 'container/LogsExplorerViews';
import { import {
defaultLogsSelectedColumns, defaultLogsSelectedColumns,
defaultOptionsQuery, defaultOptionsQuery,
@ -20,6 +21,7 @@ import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions'; import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import Toolbar from 'container/Toolbar/Toolbar'; import Toolbar from 'container/Toolbar/Toolbar';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import useUrlQueryData from 'hooks/useUrlQueryData'; import useUrlQueryData from 'hooks/useUrlQueryData';
import { isEqual, isNull } from 'lodash-es'; import { isEqual, isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
@ -28,13 +30,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
import { WrapperStyled } from './styles'; import { ExplorerViews } from './utils';
import { SELECTED_VIEWS } from './utils';
function LogsExplorer(): JSX.Element { function LogsExplorer(): JSX.Element {
const [showFrequencyChart, setShowFrequencyChart] = useState(true); const [selectedView, setSelectedView] = useState<ExplorerViews>(
const [selectedView, setSelectedView] = useState<SELECTED_VIEWS>( ExplorerViews.LIST,
SELECTED_VIEWS.QUERY_BUILDER_V2,
); );
const { preferences, loading: preferencesLoading } = usePreferenceContext(); const { preferences, loading: preferencesLoading } = usePreferenceContext();
@ -48,7 +48,9 @@ function LogsExplorer(): JSX.Element {
return true; return true;
}); });
const { handleRunQuery, currentQuery } = useQueryBuilder(); const { handleRunQuery, handleSetConfig } = useQueryBuilder();
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const listQueryKeyRef = useRef<any>(); const listQueryKeyRef = useRef<any>();
@ -56,13 +58,19 @@ function LogsExplorer(): JSX.Element {
const [isLoadingQueries, setIsLoadingQueries] = useState<boolean>(false); const [isLoadingQueries, setIsLoadingQueries] = useState<boolean>(false);
const handleToggleShowFrequencyChart = (): void => { const handleChangeSelectedView = useCallback(
setShowFrequencyChart(!showFrequencyChart); (view: ExplorerViews): void => {
}; if (selectedView === ExplorerViews.LIST) {
handleSetConfig(PANEL_TYPES.LIST, DataSource.LOGS);
}
const handleChangeSelectedView = (view: SELECTED_VIEWS): void => { setSelectedView(view);
setSelectedView(view); handleExplorerTabChange(
}; view === ExplorerViews.TIMESERIES ? PANEL_TYPES.TIME_SERIES : view,
);
},
[handleSetConfig, handleExplorerTabChange, selectedView],
);
const handleFilterVisibilityChange = (): void => { const handleFilterVisibilityChange = (): void => {
setLocalStorageApi( setLocalStorageApi(
@ -72,19 +80,6 @@ function LogsExplorer(): JSX.Element {
setShowFilters((prev) => !prev); setShowFilters((prev) => !prev);
}; };
// Switch to query builder view if there are more than 1 queries
useEffect(() => {
if (currentQuery.builder.queryData.length > 1) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER_V2);
}
if (
currentQuery.builder.queryData.length === 1 &&
currentQuery.builder.queryData?.[0]?.groupBy?.length > 0
) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER_V2);
}
}, [currentQuery.builder.queryData, currentQuery.builder.queryData.length]);
const { const {
redirectWithQuery: redirectWithOptionsData, redirectWithQuery: redirectWithOptionsData,
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery); } = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
@ -195,42 +190,44 @@ function LogsExplorer(): JSX.Element {
preferencesLoading, preferencesLoading,
]); ]);
const isMultipleQueries = useMemo(
() =>
currentQuery.builder.queryData?.length > 1 ||
currentQuery.builder.queryFormulas?.length > 0,
[currentQuery],
);
const isGroupByPresent = useMemo(
() =>
currentQuery.builder.queryData?.length === 1 &&
currentQuery.builder.queryData?.[0]?.groupBy?.length > 0,
[currentQuery.builder.queryData],
);
const toolbarViews = useMemo( const toolbarViews = useMemo(
() => ({ () => ({
search: { list: {
name: 'search', name: 'list',
label: 'Search', label: 'List',
disabled: isMultipleQueries || isGroupByPresent,
show: true, show: true,
key: 'list',
}, },
queryBuilder: { timeseries: {
name: 'query-builder', name: 'timeseries',
label: 'Query Builder', label: 'Timeseries',
disabled: false, disabled: false,
show: true, show: true,
key: 'timeseries',
},
trace: {
name: 'trace',
label: 'Trace',
disabled: false,
show: false,
key: 'trace',
},
table: {
name: 'table',
label: 'Table',
disabled: false,
show: true,
key: 'table',
}, },
clickhouse: { clickhouse: {
name: 'clickhouse', name: 'clickhouse',
label: 'Clickhouse', label: 'Clickhouse',
disabled: false, disabled: false,
show: false, show: false,
key: 'clickhouse',
}, },
}), }),
[isGroupByPresent, isMultipleQueries], [],
); );
return ( return (
@ -256,8 +253,6 @@ function LogsExplorer(): JSX.Element {
items={toolbarViews} items={toolbarViews}
selectedView={selectedView} selectedView={selectedView}
onChangeSelectedView={handleChangeSelectedView} onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart={showFrequencyChart}
/> />
} }
rightActions={ rightActions={
@ -271,24 +266,21 @@ function LogsExplorer(): JSX.Element {
showOldCTA showOldCTA
/> />
<WrapperStyled> <div className="log-explorer-query-container">
<div className="log-explorer-query-container"> <div>
<div> <ExplorerCard sourcepage={DataSource.LOGS}>
<ExplorerCard sourcepage={DataSource.LOGS}> <LogExplorerQuerySection selectedView={selectedView} />
<LogExplorerQuerySection selectedView={selectedView} /> </ExplorerCard>
</ExplorerCard>
</div>
<div className="logs-explorer-views">
<LogsExplorerViews
selectedView={selectedView}
showFrequencyChart={showFrequencyChart}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
setIsLoadingQueries={setIsLoadingQueries}
/>
</div>
</div> </div>
</WrapperStyled> <div className="logs-explorer-views">
<LogsExplorerViewsContainer
selectedView={selectedView}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
setIsLoadingQueries={setIsLoadingQueries}
/>
</div>
</div>
</section> </section>
</div> </div>
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>

View File

@ -17,11 +17,12 @@ export const prepareQueryWithDefaultTimestamp = (query: Query): Query => ({
}); });
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
export enum SELECTED_VIEWS { export enum ExplorerViews {
SEARCH = 'search', LIST = 'list',
QUERY_BUILDER = 'query-builder', TIMESERIES = 'timeseries',
TRACE = 'trace',
TABLE = 'table',
CLICKHOUSE = 'clickhouse', CLICKHOUSE = 'clickhouse',
QUERY_BUILDER_V2 = 'query-builder-v2',
} }
export const LogsQuickFiltersConfig: IQuickFiltersConfig[] = [ export const LogsQuickFiltersConfig: IQuickFiltersConfig[] = [

View File

@ -8,11 +8,11 @@
.ant-collapse-header-text { .ant-collapse-header-text {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
font-family: Inter; font-family: Inter;
font-size: 14px; font-size: 12px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 18px; line-height: 16px;
letter-spacing: -0.07px; letter-spacing: -0.065px;
text-transform: capitalize; text-transform: capitalize;
} }
@ -24,11 +24,11 @@
.ant-input-group-addon { .ant-input-group-addon {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
font-family: 'Space Mono', monospace; font-family: 'Space Mono', monospace;
font-size: 12px; font-size: 11px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 16px; line-height: 16px;
letter-spacing: 0.48px; letter-spacing: 0.44px;
padding: 0 6px; padding: 0 6px;
} }
@ -37,11 +37,11 @@
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
font-family: 'Space Mono', monospace; font-family: 'Space Mono', monospace;
font-size: 12px; font-size: 11px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 16px; line-height: 16px;
letter-spacing: 0.48px; letter-spacing: 0.44px;
} }
} }
} }
@ -54,7 +54,8 @@
} }
.filter-header { .filter-header {
padding: 16px 8px 16px 12px; padding: 4px 8px;
.filter-title { .filter-title {
display: flex; display: flex;
gap: 6px; gap: 6px;

View File

@ -1,9 +1,4 @@
.trace-explorer-header { .trace-explorer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 8px;
.trace-explorer-run-query { .trace-explorer-run-query {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
@ -27,17 +22,54 @@
} }
.traces-explorer-views { .traces-explorer-views {
padding: 8px;
.ant-tabs-tabpane { .ant-tabs-tabpane {
padding: 0 8px; padding: 0 8px;
} }
} }
.qb-search-view-container {
padding: 8px;
border: 1px solid var(--Slate-400, #1d212d) !important;
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--Slate-400, #1d212d) !important;
background: var(--Ink-300, #16181d) !important;
height: 34px !important;
box-sizing: border-box !important;
}
}
.trace-explorer-list-view {
flex: 1;
}
.trace-explorer-traces-view {
flex: 1;
}
.trace-explorer-table-view {
flex: 1;
}
.trace-explorer-time-series-view {
flex: 1;
}
.trace-explorer-page { .trace-explorer-page {
display: flex; display: flex;
.filter { .filter {
width: 260px; width: 260px;
height: 100vh; height: 100%;
min-height: 100vh;
border-right: 0px; border-right: 0px;
border: 1px solid var(--bg-slate-400); border: 1px solid var(--bg-slate-400);

View File

@ -1,12 +1,10 @@
import './TracesExplorer.styles.scss'; import './TracesExplorer.styles.scss';
import { FilterOutlined } from '@ant-design/icons';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Button, Card, Tabs, Tooltip } from 'antd'; import { Card } from 'antd';
import logEvent from 'api/common/logEvent'; import logEvent from 'api/common/logEvent';
import cx from 'classnames'; import cx from 'classnames';
import ExplorerCard from 'components/ExplorerCard/ExplorerCard'; import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
import QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2';
import QuickFilters from 'components/QuickFilters/QuickFilters'; import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types'; import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
import { LOCALSTORAGE } from 'constants/localStorage'; import { LOCALSTORAGE } from 'constants/localStorage';
@ -15,10 +13,16 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper'; import ExplorerOptionWrapper from 'container/ExplorerOptions/ExplorerOptionWrapper';
import ExportPanel from 'container/ExportPanel'; import ExportPanel from 'container/ExportPanel';
import { useOptionsMenu } from 'container/OptionsMenu'; import { useOptionsMenu } from 'container/OptionsMenu';
import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions/LeftToolbarActions';
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions'; import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2'; import TimeSeriesView from 'container/TimeSeriesView';
import Toolbar from 'container/Toolbar/Toolbar';
import ListView from 'container/TracesExplorer/ListView';
// import DateTimeSelector from 'container/TopNav/DateTimeSelectionV2';
import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs'; import { defaultSelectedColumns } from 'container/TracesExplorer/ListView/configs';
import QuerySection from 'container/TracesExplorer/QuerySection'; import QuerySection from 'container/TracesExplorer/QuerySection';
import TableView from 'container/TracesExplorer/TableView';
import TracesView from 'container/TracesExplorer/TracesView';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils'; import { addEmptyWidgetInDashboardJSONWithQuery } from 'hooks/dashboard/utils';
import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam'; import { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
@ -35,9 +39,6 @@ import { DataSource } from 'types/common/queryBuilder';
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink'; import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { ActionsWrapper, Container } from './styles';
import { getTabsItems } from './utils';
function TracesExplorer(): JSX.Element { function TracesExplorer(): JSX.Element {
const { const {
currentQuery, currentQuery,
@ -57,12 +58,11 @@ function TracesExplorer(): JSX.Element {
}); });
const currentPanelType = useGetPanelTypesQueryParam(); const currentPanelType = useGetPanelTypesQueryParam();
const currentTab = panelType || PANEL_TYPES.LIST;
const { handleExplorerTabChange } = useHandleExplorerTabChange(); const { handleExplorerTabChange } = useHandleExplorerTabChange();
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
const currentTab = panelType || PANEL_TYPES.LIST;
const listQuery = useMemo(() => { const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null; if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
@ -106,11 +106,6 @@ function TracesExplorer(): JSX.Element {
}; };
}, [updateAllQueriesOperators]); }, [updateAllQueriesOperators]);
const tabsItems = getTabsItems({
isListViewDisabled: isMultipleQueries || isGroupByExist,
isFilterApplied: !isEmpty(listQuery?.filters.items),
});
const exportDefaultQuery = useMemo( const exportDefaultQuery = useMemo(
() => () =>
updateAllQueriesOperators( updateAllQueriesOperators(
@ -184,8 +179,10 @@ function TracesExplorer(): JSX.Element {
handleExplorerTabChange, handleExplorerTabChange,
currentPanelType, currentPanelType,
]); ]);
const [isOpen, setOpen] = useState<boolean>(true); const [isOpen, setOpen] = useState<boolean>(true);
const logEventCalledRef = useRef(false); const logEventCalledRef = useRef(false);
useEffect(() => { useEffect(() => {
if (!logEventCalledRef.current) { if (!logEventCalledRef.current) {
logEvent('Traces Explorer: Page visited', {}); logEvent('Traces Explorer: Page visited', {});
@ -193,6 +190,50 @@ function TracesExplorer(): JSX.Element {
} }
}, []); }, []);
const toolbarViews = useMemo(
() => ({
list: {
name: 'list',
label: 'List',
show: true,
key: 'list',
},
timeseries: {
name: 'timeseries',
label: 'Timeseries',
disabled: false,
show: true,
key: 'timeseries',
},
trace: {
name: 'trace',
label: 'Trace',
disabled: false,
show: true,
key: 'trace',
},
table: {
name: 'table',
label: 'Table',
disabled: false,
show: true,
key: 'table',
},
clickhouse: {
name: 'clickhouse',
label: 'Clickhouse',
disabled: false,
show: false,
key: 'clickhouse',
},
}),
[],
);
const isFilterApplied = useMemo(() => !isEmpty(listQuery?.filters.items), [
listQuery,
]);
return ( return (
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}> <Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="trace-explorer-page"> <div className="trace-explorer-page">
@ -206,55 +247,87 @@ function TracesExplorer(): JSX.Element {
}} }}
/> />
</Card> </Card>
<Card <div
className={cx('trace-explorer', { className={cx('trace-explorer', {
'filters-expanded': isOpen, 'filters-expanded': isOpen,
})} })}
> >
<div className={`trace-explorer-header ${isOpen ? 'single-child' : ''}`}> <div className="trace-explorer-header">
{!isOpen && ( <Toolbar
<Tooltip title="Expand filters" placement="right"> showAutoRefresh
<Button leftActions={
onClick={(): void => setOpen(!isOpen)} <LeftToolbarActions
className="filter-outlined-btn" showFilter={isOpen}
data-testid="filter-uncollapse-btn" handleFilterVisibilityChange={(): void => setOpen(!isOpen)}
> items={toolbarViews}
<FilterOutlined /> selectedView={
</Button> currentTab === PANEL_TYPES.TIME_SERIES ? 'timeseries' : currentTab
</Tooltip> }
)} onChangeSelectedView={(view): void => {
<div className="trace-explorer-run-query"> if (view === 'timeseries') {
<RightToolbarActions onStageRunQuery={handleRunQuery} /> handleExplorerTabChange(PANEL_TYPES.TIME_SERIES);
<DateTimeSelector showAutoRefresh /> // setSelectedView(PANEL_TYPES.TIME_SERIES);
</div> } else {
handleExplorerTabChange((view as unknown) as PANEL_TYPES);
// setSelectedView((view as unknown) as PANEL_TYPES);
}
}}
/>
}
rightActions={<RightToolbarActions onStageRunQuery={handleRunQuery} />}
// showOldCTA={false}
/>
</div> </div>
<ExplorerCard sourcepage={DataSource.TRACES}> <ExplorerCard sourcepage={DataSource.TRACES}>
{/* <QuerySection /> */} <div className="query-section-container">
<QueryBuilderV2 <QuerySection selectedView={currentTab} />
source={DataSource.TRACES} </div>
query={currentQuery.builder.queryData[0]}
/>
</ExplorerCard> </ExplorerCard>
<Container className="traces-explorer-views"> <div className="traces-explorer-views">
<ActionsWrapper> <div className="traces-explorer-export-panel">
<ExportPanel query={exportDefaultQuery} onExport={handleExport} /> <ExportPanel
</ActionsWrapper> query={exportDefaultQuery}
isLoading={false}
onExport={handleExport}
/>
</div>
{currentTab === PANEL_TYPES.LIST && (
<div className="trace-explorer-list-view">
<ListView isFilterApplied={isFilterApplied} />
</div>
)}
{currentTab === PANEL_TYPES.TRACE && (
<div className="trace-explorer-traces-view">
<TracesView isFilterApplied={isFilterApplied} />
</div>
)}
{currentTab === PANEL_TYPES.TIME_SERIES && (
<div className="trace-explorer-time-series-view">
<TimeSeriesView
dataSource={DataSource.TRACES}
isFilterApplied={isFilterApplied}
/>
</div>
)}
{currentTab === PANEL_TYPES.TABLE && (
<div className="trace-explorer-table-view">
<TableView />
</div>
)}
</div>
<Tabs
defaultActiveKey={currentTab}
activeKey={currentTab}
items={tabsItems}
onChange={handleExplorerTabChange}
/>
</Container>
<ExplorerOptionWrapper <ExplorerOptionWrapper
disabled={!stagedQuery} disabled={!stagedQuery}
query={exportDefaultQuery} query={exportDefaultQuery}
sourcepage={DataSource.TRACES} sourcepage={DataSource.TRACES}
onExport={handleExport} onExport={handleExport}
/> />
</Card> </div>
</div> </div>
</Sentry.ErrorBoundary> </Sentry.ErrorBoundary>
); );