feat: query builder layout updates

This commit is contained in:
Yunus M 2025-05-25 18:43:36 +05:30
parent 3e72d3fd02
commit 7254772e70
9 changed files with 493 additions and 17 deletions

View File

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

View File

@ -1,20 +1,171 @@
import './LogsQB.styles.scss';
import { Button, Dropdown } from 'antd';
import { ENTITY_VERSION_V4 } from 'constants/app';
import QBEntityOptions from 'container/QueryBuilder/components/QBEntityOptions/QBEntityOptions';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { Copy, Ellipsis, Plus, Sigma, Trash } from 'lucide-react';
import { memo, useCallback, useState } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import QueryAddOns from '../QueryAddOns/QueryAddOns';
import QueryAggregation from '../QueryAggregation/QueryAggregation';
import QuerySearch from '../QuerySearch/QuerySearch';
import { Formula } from 'container/QueryBuilder/components/Formula/Formula';
export const LogsQB = memo(function LogsQB({
query,
filterConfigs,
}: {
query: IBuilderQuery;
filterConfigs: QueryBuilderProps['filterConfigs'];
}): JSX.Element {
const showFunctions = query?.functions?.length > 0;
const version = ENTITY_VERSION_V4;
const {
currentQuery,
cloneQuery,
addNewFormula,
addNewBuilderQuery,
} = useQueryBuilder();
const [isCollapsed, setIsCollapsed] = useState(false);
console.log('isCollapsed', isCollapsed);
const isListViewPanel = false;
const index = 0;
const {
isMetricsDataSource,
handleChangeQueryData,
handleDeleteQuery,
handleQueryFunctionsUpdates,
} = useQueryOperations({
index,
query,
filterConfigs,
isListViewPanel,
entityVersion: version,
});
const handleToggleDisableQuery = useCallback(() => {
handleChangeQueryData('disabled', !query.disabled);
}, [handleChangeQueryData, query]);
const handleToggleCollapsQuery = (): void => {
setIsCollapsed(!isCollapsed);
};
function LogsQB({ query }: { query: IBuilderQuery }): JSX.Element {
return (
<div className="logs-qb">
<QuerySearch />
<QueryAggregation source={DataSource.LOGS} />
<QueryAddOns query={query} version="v3" isListViewPanel={false} />
<div className="qb-content-container">
<div className="qb-content-section">
<div className="qb-header-container">
<div className="query-actions-container">
<div className="query-actions-left-container">
<QBEntityOptions
isMetricsDataSource={isMetricsDataSource}
showFunctions={
(version && version === ENTITY_VERSION_V4) ||
query.dataSource === DataSource.LOGS ||
showFunctions ||
false
}
isCollapsed={isCollapsed}
entityType="query"
entityData={query}
onToggleVisibility={handleToggleDisableQuery}
onDelete={handleDeleteQuery}
onCloneQuery={cloneQuery}
onCollapseEntity={handleToggleCollapsQuery}
query={query}
onQueryFunctionsUpdates={handleQueryFunctionsUpdates}
showDeleteButton={currentQuery.builder.queryData.length > 1}
isListViewPanel={isListViewPanel}
index={index}
/>
</div>
<Dropdown
className="query-actions-dropdown"
menu={{
items: [
{
label: 'Clone',
key: 'clone-query',
icon: <Copy size={14} />,
},
{
label: 'Delete',
key: 'delete-query',
icon: <Trash size={14} />,
},
],
}}
placement="bottomRight"
>
<Ellipsis size={16} />
</Dropdown>
</div>
</div>
<div className="qb-elements-container">
<QuerySearch />
<QueryAggregation source={DataSource.LOGS} />
<QueryAddOns query={query} version="v3" isListViewPanel={false} />
</div>
</div>
<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>
<div className="qb-footer">
<div className="qb-footer-container">
<div className="qb-add-new-query">
<Button
className="add-new-query-button periscope-btn secondary"
type="text"
icon={<Plus size={16} />}
onClick={addNewBuilderQuery}
/>
</div>
<div className="qb-add-formula">
<Button
className="add-formula-button periscope-btn secondary"
icon={<Sigma size={16} />}
onClick={addNewFormula}
>
Add Formula
</Button>
</div>
</div>
</div>
</div>
<div className="query-names-section" />
</div>
);
}
export default LogsQB;
});

View File

@ -14,13 +14,20 @@
font-size: var(--font-size-xs);
font-style: normal;
font-weight: var(--font-weight-normal);
color: var(--Vanilla-400, #c0c1c3);
}
.tab {
border: 1px solid var(--bg-slate-400);
border-left: none;
min-width: 114px;
height: 36px;
line-height: 36px;
&:first-child {
border-left: 1px solid var(--bg-slate-400);
}
}
.tab::before {

View File

@ -7,7 +7,6 @@
height: 100%;
display: flex;
flex-direction: column;
padding: 8px;
gap: 4px;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', sans-serif;
@ -15,4 +14,279 @@
border: 1px solid var(--bg-slate-400);
border-right: none;
border-left: none;
.qb-content-container {
display: flex;
flex-direction: column;
flex: 1;
position: relative;
}
.qb-content-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
flex: 1;
.qb-header-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-left: 32px;
.query-actions-container {
display: flex;
flex-direction: row;
gap: 8px;
justify-content: space-between;
align-items: center;
width: 100%;
}
}
.qb-elements-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 108px;
.code-mirror-where-clause,
.query-aggregation-container,
.query-add-ons {
position: relative;
&::before {
content: '';
position: absolute;
left: -10px;
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted #1d212d;
}
/* Horizontal line pointing from vertical to the item */
&::after {
content: '';
position: absolute;
left: -28px;
top: 15px;
width: 24px;
height: 1px;
background: repeating-linear-gradient(
to right,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
}
}
}
}
.query-names-section {
display: flex;
flex-direction: column;
gap: 8px;
width: 44px;
padding-left: 8px;
border-left: 1px solid var(--bg-slate-400);
}
.qb-formulas-container {
display: flex;
flex-direction: column;
gap: 8px;
margin-left: 32px;
padding-bottom: 16px;
.qb-formula {
.ant-row {
row-gap: 0px !important;
}
.qb-entity-options {
margin-left: 8px;
padding-right: 8px;
}
.formula-container {
margin-left: 82px;
padding: 4px 0px;
.ant-col {
&::before {
content: '';
position: absolute;
left: -10px;
top: 12px;
width: 6px;
height: 6px;
border-left: 6px dotted #1d212d;
}
/* Horizontal line pointing from vertical to the item */
&::after {
content: '';
position: absolute;
left: -28px;
top: 15px;
width: 24px;
height: 1px;
background: repeating-linear-gradient(
to right,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
}
}
.formula-expression {
border-bottom-left-radius: 0px !important;
border-bottom-right-radius: 0px !important;
resize: none;
}
.formula-legend {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
.ant-input-group-addon {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
}
.ant-input {
border-top-left-radius: 0px !important;
border-top-right-radius: 0px !important;
}
}
}
}
}
.qb-footer {
padding: 0 8px 16px 8px;
.qb-footer-container {
display: flex;
flex-direction: row;
gap: 8px;
margin-left: 32px;
.qb-add-new-query {
display: flex;
flex-direction: row;
gap: 8px;
&::before {
content: '';
height: calc(100% - 82px);
content: '';
position: absolute;
left: 56px;
top: 31px;
bottom: 0;
width: 1px;
background: repeating-linear-gradient(
to bottom,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
}
}
}
}
.qb-entity-options {
display: flex;
flex-direction: row;
gap: 8px;
.options {
.query-name.sync-btn {
border-radius: 0px 2px 2px 0px !important;
border: 1px solid rgba(242, 71, 105, 0.2) !important;
background: rgba(242, 71, 105, 0.1) !important;
color: var(--Sakura-400, #f56c87) !important;
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
text-transform: uppercase;
&::before {
content: '';
height: 120px;
content: '';
position: absolute;
left: 0;
top: 31px;
bottom: 0;
width: 1px;
background: repeating-linear-gradient(
to bottom,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
left: 15px;
}
}
.formula-name {
border-radius: 0px 2px 2px 0px !important;
border: 1px solid rgba(242, 71, 105, 0.2) !important;
background: rgba(242, 71, 105, 0.1) !important;
color: var(--Sakura-400, #f56c87) !important;
font-family: 'Space Mono';
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 128.571% */
text-transform: uppercase;
&::before {
content: '';
height: 80px;
content: '';
position: absolute;
left: 0;
top: 31px;
bottom: 0;
width: 1px;
background: repeating-linear-gradient(
to bottom,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
left: 15px;
}
}
}
}
}

View File

@ -1,9 +1,12 @@
import './QueryBuilderV2.styles.scss';
import { OPERATORS } from 'constants/queryBuilder';
import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces';
import { useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import LogsQB from './Logs/LogsQB';
import { LogsQB } from './Logs/LogsQB';
import MetricsQB from './Metrics/MetricsQB';
import { QueryBuilderV2Provider } from './QueryBuilderV2Context';
import TracesQB from './Traces/TracesQB';
@ -21,10 +24,46 @@ function QueryBuilderV2Main({
const isLogsDataSource = source === DataSource.LOGS;
const isTracesDataSource = source === DataSource.TRACES;
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,
},
};
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 query={query} /> : null}
{isLogsDataSource ? <LogsQB query={query} /> : null}
{isLogsDataSource ? (
<LogsQB
query={query}
filterConfigs={
query.dataSource === DataSource.TRACES
? listViewTracesFilterConfigs
: listViewLogFilterConfigs
}
/>
) : null}
{isTracesDataSource ? <TracesQB query={query} /> : null}
</div>
);

View File

@ -161,6 +161,7 @@ export function Formula({
<Col span={24}>
<Input.TextArea
name="expression"
className="formula-expression"
onChange={handleChange}
size="middle"
value={formula.expression}
@ -170,6 +171,7 @@ export function Formula({
<Col span={24}>
<Input
name="legend"
className="formula-legend"
onChange={handleChange}
size="middle"
value={formula.legend}

View File

@ -28,9 +28,12 @@
border-radius: 2px;
.periscope-btn {
border: 1px solid var(--bg-slate-200);
background: var(--bg-ink-200);
min-width: 32px;
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);
}
.query-name {

View File

@ -96,14 +96,14 @@ export default function QBEntityOptions({
{entityData.disabled ? <EyeOff size={16} /> : <Eye size={16} />}
</Button>
</Tooltip>
{/*
{entityType === 'query' && (
<Tooltip title={`Clone Query ${entityData.queryName}`}>
<Button className={cx('periscope-btn')} onClick={handleCloneEntity}>
<Copy size={14} />
</Button>
</Tooltip>
)}
)} */}
<Button
className={cx(

View File

@ -75,13 +75,13 @@ function LogsExplorer(): JSX.Element {
// Switch to query builder view if there are more than 1 queries
useEffect(() => {
if (currentQuery.builder.queryData.length > 1) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER);
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER_V2);
}
if (
currentQuery.builder.queryData.length === 1 &&
currentQuery.builder.queryData?.[0]?.groupBy?.length > 0
) {
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER);
handleChangeSelectedView(SELECTED_VIEWS.QUERY_BUILDER_V2);
}
}, [currentQuery.builder.queryData, currentQuery.builder.queryData.length]);