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 {
display: flex;
flex-direction: column;
gap: 1rem;
flex-direction: row;
gap: 4px;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
gap: 4px;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
@ -160,6 +157,7 @@
margin-left: 32px;
padding-bottom: 16px;
padding-left: 8px;
.qb-formula {
.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 {

View File

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

View File

@ -17,16 +17,20 @@ import QuerySearch from './QuerySearch/QuerySearch';
export const QueryV2 = memo(function QueryV2({
index,
queryVariant,
query,
filterConfigs,
queryComponents,
isListViewPanel = false,
version,
showSpanScopeSelector = false,
source,
}: QueryProps & { source: DataSource }): JSX.Element {
}: QueryProps): JSX.Element {
const { cloneQuery } = useQueryBuilder();
const showFunctions = query?.functions?.length > 0;
const { dataSource } = query;
console.log('queryComponents', queryComponents);
const [isCollapsed, setIsCollapsed] = useState(false);
@ -34,6 +38,7 @@ export const QueryV2 = memo(function QueryV2({
handleChangeQueryData,
handleDeleteQuery,
handleQueryFunctionsUpdates,
handleChangeDataSource,
} = useQueryOperations({
index,
query,
@ -61,7 +66,7 @@ export const QueryV2 = memo(function QueryV2({
<div className="query-actions-container">
<div className="query-actions-left-container">
<QBEntityOptions
isMetricsDataSource={source === DataSource.METRICS}
isMetricsDataSource={dataSource === DataSource.METRICS}
showFunctions={
(version && version === ENTITY_VERSION_V4) ||
query.dataSource === DataSource.LOGS ||
@ -81,6 +86,8 @@ export const QueryV2 = memo(function QueryV2({
showCloneOption={false}
isListViewPanel={isListViewPanel}
index={index}
queryVariant={queryVariant}
onChangeDataSource={handleChangeDataSource}
/>
</div>
@ -111,7 +118,7 @@ export const QueryV2 = memo(function QueryV2({
<div className="qb-elements-container">
<div className="qb-search-container">
{source === DataSource.METRICS && (
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect query={query} index={0} version="v4" />
</div>
@ -129,9 +136,9 @@ export const QueryV2 = memo(function QueryV2({
</div>
</div>
<QueryAggregation source={source} />
<QueryAggregation source={dataSource} />
{source === DataSource.METRICS && (
{dataSource === DataSource.METRICS && (
<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 QueryBuilderV2 from 'components/QueryBuilderV2/QueryBuilderV2';
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import {
initialQueriesMap,
OPERATORS,
PANEL_TYPES,
} from 'constants/queryBuilder';
import ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder';
import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces';
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
@ -16,8 +15,8 @@ import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import {
ExplorerViews,
prepareQueryWithDefaultTimestamp,
SELECTED_VIEWS,
} from 'pages/LogsExplorer/utils';
import { memo, useCallback, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
@ -26,7 +25,7 @@ import { DataSource } from 'types/common/queryBuilder';
function LogExplorerQuerySection({
selectedView,
}: {
selectedView: SELECTED_VIEWS;
selectedView: ExplorerViews;
}): JSX.Element {
const { currentQuery, updateAllQueriesOperators } = useQueryBuilder();
@ -89,7 +88,7 @@ function LogExplorerQuerySection({
return (
<>
{selectedView === SELECTED_VIEWS.SEARCH && (
{selectedView === ExplorerViews.LIST && (
<div className="qb-search-view-container">
<QueryBuilderSearchV2
query={query}
@ -99,25 +98,18 @@ function LogExplorerQuerySection({
</div>
)}
{selectedView === SELECTED_VIEWS.QUERY_BUILDER && (
<QueryBuilder
panelType={panelTypes}
{(selectedView === ExplorerViews.TABLE ||
selectedView === ExplorerViews.TIMESERIES ||
selectedView === ExplorerViews.CLICKHOUSE) && (
<QueryBuilderV2
isListViewPanel={panelTypes === PANEL_TYPES.LIST}
config={{ initialDataSource: DataSource.LOGS, queryVariant: 'static' }}
panelType={panelTypes}
filterConfigs={filterConfigs}
queryComponents={queryComponents}
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;
}
}
}
.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 {
display: flex;
align-items: center;
gap: 12px;
align-self: flex-end;
.rows {
color: var(--bg-vanilla-400);
font-family: 'Geist Mono';
@ -110,13 +154,6 @@
letter-spacing: 0.36px;
}
}
}
.logs-actions-container {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
.ant-btn {
border: none;
@ -186,6 +223,14 @@
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 {
.rows {
color: var(--bg-ink-400);

View File

@ -1,7 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './LogsExplorerViews.styles.scss';
import { Button, Typography } from 'antd';
import { Button, Switch, Typography } from 'antd';
import { getQueryStats, WsDataEvent } from 'api/common/getQueryStats';
import logEvent from 'api/common/logEvent';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
@ -47,7 +47,7 @@ import {
set,
} from 'lodash-es';
import { Sliders } from 'lucide-react';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
import { ExplorerViews } from 'pages/LogsExplorer/utils';
import { useTimezone } from 'providers/Timezone';
import {
memo,
@ -80,15 +80,13 @@ import { v4 } from 'uuid';
import QueryStatus from './QueryStatus';
function LogsExplorerViews({
function LogsExplorerViewsContainer({
selectedView,
showFrequencyChart,
setIsLoadingQueries,
listQueryKeyRef,
chartQueryKeyRef,
}: {
selectedView: SELECTED_VIEWS;
showFrequencyChart: boolean;
selectedView: ExplorerViews;
setIsLoadingQueries: React.Dispatch<React.SetStateAction<boolean>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
listQueryKeyRef: MutableRefObject<any>;
@ -97,6 +95,7 @@ function LogsExplorerViews({
}): JSX.Element {
const { safeNavigate } = useSafeNavigate();
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.
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
@ -240,15 +239,6 @@ function LogsExplorerViews({
[currentQuery, selectedPanelType, updateAllQueriesOperators],
);
const handleModeChange = (panelType: PANEL_TYPES): void => {
if (selectedView === SELECTED_VIEWS.SEARCH) {
handleSetConfig(panelType, DataSource.LOGS);
}
setShowFormatMenuItems(false);
handleExplorerTabChange(panelType);
};
const {
data: listChartData,
isFetching: isFetchingListChartData,
@ -460,8 +450,7 @@ function LogsExplorerViews({
useEffect(() => {
const shouldChangeView =
(isMultipleQueries || isGroupByExist) &&
selectedView !== SELECTED_VIEWS.SEARCH;
(isMultipleQueries || isGroupByExist) && selectedView !== ExplorerViews.LIST;
if (selectedPanelType === PANEL_TYPES.LIST && shouldChangeView) {
handleExplorerTabChange(PANEL_TYPES.TIME_SERIES);
@ -481,11 +470,7 @@ function LogsExplorerViews({
]);
useEffect(() => {
if (
selectedView &&
selectedView === SELECTED_VIEWS.SEARCH &&
handleSetConfig
) {
if (selectedView && selectedView === ExplorerViews.LIST && handleSetConfig) {
handleSetConfig(defaultTo(panelTypes, PANEL_TYPES.LIST), DataSource.LOGS);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@ -651,111 +636,90 @@ function LogsExplorerViews({
return (
<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="views-tabs-container">
<Button.Group className="views-tabs">
<Button
value={PANEL_TYPES.LIST}
className={
// eslint-disable-next-line sonarjs/no-duplicate-string
selectedPanelType === PANEL_TYPES.LIST ? 'selected_view tab' : 'tab'
}
disabled={
(isMultipleQueries || isGroupByExist) && selectedView !== 'search'
}
onClick={(): void => handleModeChange(PANEL_TYPES.LIST)}
data-testid="logs-list-view"
>
List view
</Button>
<Button
value={PANEL_TYPES.TIME_SERIES}
className={
// eslint-disable-next-line sonarjs/no-duplicate-string
selectedPanelType === PANEL_TYPES.TIME_SERIES
? 'selected_view tab'
: 'tab'
}
onClick={(): void => handleModeChange(PANEL_TYPES.TIME_SERIES)}
data-testid="time-series-view"
>
Time series
</Button>
<Button
value={PANEL_TYPES.TABLE}
className={
// eslint-disable-next-line sonarjs/no-duplicate-string
selectedPanelType === PANEL_TYPES.TABLE ? 'selected_view tab' : 'tab'
}
onClick={(): void => handleModeChange(PANEL_TYPES.TABLE)}
data-testid="table-view"
>
Table
</Button>
</Button.Group>
<div className="logs-actions-container">
{selectedPanelType === PANEL_TYPES.LIST && (
<div className="tab-options">
<Download
data={flattenLogData}
isLoading={isFetching}
fileName="log_data"
/>
<div className="format-options-container" ref={menuRef}>
<Button
className="periscope-btn"
onClick={handleToggleShowFormatOptions}
icon={<Sliders size={14} />}
data-testid="periscope-btn"
<div className="logs-actions-container">
<div className="tab-options">
<div className="tab-options-left">
{selectedPanelType === PANEL_TYPES.LIST && (
<div className="frequency-chart-view-controller">
<Typography>Frequency chart</Typography>
<Switch
size="small"
checked={showFrequencyChart}
defaultChecked
onChange={(): void => setShowFrequencyChart(!showFrequencyChart)}
/>
</div>
)}
</div>
<div className="tab-options-right">
{selectedPanelType === PANEL_TYPES.LIST && (
<>
<Download
data={flattenLogData}
isLoading={isFetching}
fileName="log_data"
/>
<div className="format-options-container" ref={menuRef}>
<Button
className="periscope-btn"
onClick={handleToggleShowFormatOptions}
icon={<Sliders size={14} />}
data-testid="periscope-btn"
/>
{showFormatMenuItems && (
<LogsFormatOptionsMenu
title="FORMAT"
items={formatItems}
selectedOptionFormat={options.format}
config={config}
/>
)}
</div>
</>
)}
{(selectedPanelType === PANEL_TYPES.TIME_SERIES ||
selectedPanelType === PANEL_TYPES.TABLE) && (
<div className="query-stats">
<QueryStatus
loading={isLoading || isFetching}
error={isError}
success={isSuccess}
/>
{showFormatMenuItems && (
<LogsFormatOptionsMenu
title="FORMAT"
items={formatItems}
selectedOptionFormat={options.format}
config={config}
/>
{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>
)}
{(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>
{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">
{selectedPanelType === PANEL_TYPES.LIST && (
<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 { server } from 'mocks-server/server';
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 { QueryBuilderContext } from 'providers/QueryBuilder';
import { VirtuosoMockContext } from 'react-virtuoso';
@ -127,8 +127,7 @@ const renderer = (): RenderResult =>
>
<PreferenceContextProvider>
<LogsExplorerViews
selectedView={SELECTED_VIEWS.SEARCH}
showFrequencyChart
selectedView={ExplorerViews.LIST}
setIsLoadingQueries={(): void => {}}
listQueryKeyRef={{ current: {} }}
chartQueryKeyRef={{ current: {} }}
@ -212,8 +211,7 @@ describe('LogsExplorerViews -', () => {
<QueryBuilderContext.Provider value={mockQueryBuilderContextValue}>
<PreferenceContextProvider>
<LogsExplorerViews
selectedView={SELECTED_VIEWS.SEARCH}
showFrequencyChart
selectedView={ExplorerViews.LIST}
setIsLoadingQueries={(): void => {}}
listQueryKeyRef={{ current: {} }}
chartQueryKeyRef={{ current: {} }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import { render, screen } from '@testing-library/react';
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 LeftToolbarActions from '../LeftToolbarActions';
@ -9,7 +9,6 @@ import RightToolbarActions from '../RightToolbarActions';
describe('ToolbarActions', () => {
it('LeftToolbarActions - renders correctly with default props', async () => {
const handleChangeSelectedView = jest.fn();
const handleToggleShowFrequencyChart = jest.fn();
const { queryByTestId } = render(
<LeftToolbarActions
items={{
@ -31,10 +30,8 @@ describe('ToolbarActions', () => {
disabled: false,
},
}}
selectedView={SELECTED_VIEWS.SEARCH}
selectedView={ExplorerViews.LIST}
onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart
showFilter
handleFilterVisibilityChange={(): void => {}}
/>,
@ -77,10 +74,8 @@ describe('ToolbarActions', () => {
show: true,
},
}}
selectedView={SELECTED_VIEWS.QUERY_BUILDER}
selectedView={ExplorerViews.TIMESERIES}
onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart
showFilter
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 { getTimeRange } from 'utils/getTimeRange';
import { Container } from './styles';
function TimeSeriesView({
data,
isLoading,
@ -162,7 +160,7 @@ function TimeSeriesView({
});
return (
<Container>
<div className="time-series-view">
{isError && <LogsError />}
<div
className="graph-container"
@ -204,7 +202,7 @@ function TimeSeriesView({
!isEmpty(chartData?.[0]) &&
chartOptions && <Uplot data={chartData} options={chartOptions} />}
</div>
</Container>
</div>
);
}

View File

@ -1,3 +1,5 @@
import './TimeSeriesView.styles.scss';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
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 { ResizeTable } from 'components/ResizeTable';
import { ENTITY_VERSION_V4 } from 'constants/app';
@ -167,12 +169,14 @@ function ListView({ isFilterApplied }: ListViewProps): JSX.Element {
return (
<Container>
{transformedQueryTableData.length !== 0 && (
<TraceExplorerControls
isLoading={isFetching}
totalCount={totalCount}
config={config}
perPageOptions={PER_PAGE_OPTIONS}
/>
<div className="trace-explorer-controls">
<TraceExplorerControls
isLoading={isFetching}
totalCount={totalCount}
config={config}
perPageOptions={PER_PAGE_OPTIONS}
/>
</div>
)}
{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 ExplorerOrderBy from 'container/ExplorerOrderBy';
import { QueryBuilder } from 'container/QueryBuilder';
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 { useGetPanelTypesQueryParam } from 'hooks/queryBuilder/useGetPanelTypesQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
// import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { memo, useCallback, useMemo } from 'react';
import { DataSource } from 'types/common/queryBuilder';
import { ButtonWrapper, Container } from './styles';
import { Container } from './styles';
function QuerySection(): JSX.Element {
const { handleRunQuery } = useQueryBuilder();
function QuerySection({
selectedView,
}: {
selectedView: PANEL_TYPES;
}): JSX.Element {
// const { currentQuery } = useQueryBuilder();
// const queryName = currentQuery?.builder?.queryData[0]?.queryName || '';
const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST);
@ -43,25 +50,30 @@ function QuerySection(): JSX.Element {
};
}, [panelTypes, renderOrderBy]);
console.log('query - section - selectedView', selectedView);
return (
<Container>
<QueryBuilder
panelType={panelTypes}
config={{
queryVariant: 'static',
initialDataSource: DataSource.TRACES,
}}
filterConfigs={filterConfigs}
{/* {(selectedView === 'list' || selectedView === 'trace') && (
<div className="qb-search-view-container">
<QuerySearch />
<div className="traces-search-filter-in">in</div>
<SpanScopeSelector queryName={queryName} />
</div>
)}
{(selectedView === 'graph' || selectedView === 'table') && ( */}
<QueryBuilderV2
isListViewPanel={panelTypes === PANEL_TYPES.LIST}
config={{ initialDataSource: DataSource.TRACES, queryVariant: 'static' }}
queryComponents={queryComponents}
panelType={panelTypes}
filterConfigs={filterConfigs}
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>
);
}

View File

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

View File

@ -8,8 +8,9 @@ import ExplorerCard from 'components/ExplorerCard/ExplorerCard';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource, SignalType } from 'components/QuickFilters/types';
import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
import LogExplorerQuerySection from 'container/LogExplorerQuerySection';
import LogsExplorerViews from 'container/LogsExplorerViews';
import LogsExplorerViewsContainer from 'container/LogsExplorerViews';
import {
defaultLogsSelectedColumns,
defaultOptionsQuery,
@ -20,6 +21,7 @@ import LeftToolbarActions from 'container/QueryBuilder/components/ToolbarActions
import RightToolbarActions from 'container/QueryBuilder/components/ToolbarActions/RightToolbarActions';
import Toolbar from 'container/Toolbar/Toolbar';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { isEqual, isNull } from 'lodash-es';
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 { DataSource } from 'types/common/queryBuilder';
import { WrapperStyled } from './styles';
import { SELECTED_VIEWS } from './utils';
import { ExplorerViews } from './utils';
function LogsExplorer(): JSX.Element {
const [showFrequencyChart, setShowFrequencyChart] = useState(true);
const [selectedView, setSelectedView] = useState<SELECTED_VIEWS>(
SELECTED_VIEWS.QUERY_BUILDER_V2,
const [selectedView, setSelectedView] = useState<ExplorerViews>(
ExplorerViews.LIST,
);
const { preferences, loading: preferencesLoading } = usePreferenceContext();
@ -48,7 +48,9 @@ function LogsExplorer(): JSX.Element {
return true;
});
const { handleRunQuery, currentQuery } = useQueryBuilder();
const { handleRunQuery, handleSetConfig } = useQueryBuilder();
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const listQueryKeyRef = useRef<any>();
@ -56,13 +58,19 @@ function LogsExplorer(): JSX.Element {
const [isLoadingQueries, setIsLoadingQueries] = useState<boolean>(false);
const handleToggleShowFrequencyChart = (): void => {
setShowFrequencyChart(!showFrequencyChart);
};
const handleChangeSelectedView = useCallback(
(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 => {
setLocalStorageApi(
@ -72,19 +80,6 @@ function LogsExplorer(): JSX.Element {
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 {
redirectWithQuery: redirectWithOptionsData,
} = useUrlQueryData<OptionsQuery>(URL_OPTIONS, defaultOptionsQuery);
@ -195,42 +190,44 @@ function LogsExplorer(): JSX.Element {
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(
() => ({
search: {
name: 'search',
label: 'Search',
disabled: isMultipleQueries || isGroupByPresent,
list: {
name: 'list',
label: 'List',
show: true,
key: 'list',
},
queryBuilder: {
name: 'query-builder',
label: 'Query Builder',
timeseries: {
name: 'timeseries',
label: 'Timeseries',
disabled: false,
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: {
name: 'clickhouse',
label: 'Clickhouse',
disabled: false,
show: false,
key: 'clickhouse',
},
}),
[isGroupByPresent, isMultipleQueries],
[],
);
return (
@ -256,8 +253,6 @@ function LogsExplorer(): JSX.Element {
items={toolbarViews}
selectedView={selectedView}
onChangeSelectedView={handleChangeSelectedView}
onToggleHistrogramVisibility={handleToggleShowFrequencyChart}
showFrequencyChart={showFrequencyChart}
/>
}
rightActions={
@ -271,24 +266,21 @@ function LogsExplorer(): JSX.Element {
showOldCTA
/>
<WrapperStyled>
<div className="log-explorer-query-container">
<div>
<ExplorerCard sourcepage={DataSource.LOGS}>
<LogExplorerQuerySection selectedView={selectedView} />
</ExplorerCard>
</div>
<div className="logs-explorer-views">
<LogsExplorerViews
selectedView={selectedView}
showFrequencyChart={showFrequencyChart}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
setIsLoadingQueries={setIsLoadingQueries}
/>
</div>
<div className="log-explorer-query-container">
<div>
<ExplorerCard sourcepage={DataSource.LOGS}>
<LogExplorerQuerySection selectedView={selectedView} />
</ExplorerCard>
</div>
</WrapperStyled>
<div className="logs-explorer-views">
<LogsExplorerViewsContainer
selectedView={selectedView}
listQueryKeyRef={listQueryKeyRef}
chartQueryKeyRef={chartQueryKeyRef}
setIsLoadingQueries={setIsLoadingQueries}
/>
</div>
</div>
</section>
</div>
</Sentry.ErrorBoundary>

View File

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

View File

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

View File

@ -1,9 +1,4 @@
.trace-explorer-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 8px;
.trace-explorer-run-query {
display: flex;
flex-direction: row-reverse;
@ -27,17 +22,54 @@
}
.traces-explorer-views {
padding: 8px;
.ant-tabs-tabpane {
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 {
display: flex;
.filter {
width: 260px;
height: 100vh;
height: 100%;
min-height: 100vh;
border-right: 0px;
border: 1px solid var(--bg-slate-400);

View File

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