@@ -33,7 +38,7 @@ function Controls({
loading={isLoading}
size="small"
type="link"
- disabled={isPreviousDisabled || isNextAndPreviousDisabled}
+ disabled={isPreviousDisabled}
onClick={handleNavigatePrevious}
>
Previous
@@ -42,7 +47,7 @@ function Controls({
loading={isLoading}
size="small"
type="link"
- disabled={isNextDisabled || isNextAndPreviousDisabled}
+ disabled={isNextDisabled}
onClick={handleNavigateNext}
>
Next
@@ -68,6 +73,7 @@ function Controls({
Controls.defaultProps = {
offset: 0,
perPageOptions: DEFAULT_PER_PAGE_OPTIONS,
+ isLogPanel: false,
};
export interface ControlsProps {
@@ -79,6 +85,7 @@ export interface ControlsProps {
handleNavigatePrevious: () => void;
handleNavigateNext: () => void;
handleCountItemsPerPageChange: (value: Pagination['limit']) => void;
+ isLogPanel?: boolean;
}
export default memo(Controls);
diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/WidgetFullView.styles.scss b/frontend/src/container/GridCardLayout/GridCard/FullView/WidgetFullView.styles.scss
index 31ec0040d8..9efb621385 100644
--- a/frontend/src/container/GridCardLayout/GridCard/FullView/WidgetFullView.styles.scss
+++ b/frontend/src/container/GridCardLayout/GridCard/FullView/WidgetFullView.styles.scss
@@ -10,7 +10,6 @@
.graph-container {
height: calc(60% - 40px);
min-height: 300px;
- border: 1px solid #333;
width: 100%;
padding: 12px;
box-sizing: border-box;
@@ -18,6 +17,11 @@
border-radius: 3px;
}
+ .list-graph-container {
+ height: calc(100% - 40px);
+ overflow-y: auto;
+ }
+
.disabled {
height: calc(100% - 65px);
}
diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
index 786ef738d6..feaf19ffad 100644
--- a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
+++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx
@@ -2,9 +2,11 @@ import './WidgetFullView.styles.scss';
import { SyncOutlined } from '@ant-design/icons';
import { Button } from 'antd';
+import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types';
import Spinner from 'components/Spinner';
import TimePreference from 'components/TimePreferenceDropDown';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import GridPanelSwitch from 'container/GridPanelSwitch';
import {
timeItems,
@@ -96,7 +98,7 @@ function FullView({
},
{
queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
- enabled: !isDependedDataLoaded,
+ enabled: !isDependedDataLoaded && widget.panelTypes !== PANEL_TYPES.LIST, // Internally both the list view panel has it's own query range api call, so we don't need to call it again
},
);
@@ -164,6 +166,8 @@ function FullView({
parentGraphVisibilityState(graphsVisibilityStates);
}, [graphsVisibilityStates, parentGraphVisibilityState]);
+ const isListView = widget.panelTypes === PANEL_TYPES.LIST;
+
if (response.isFetching) {
return ;
}
@@ -192,14 +196,17 @@ function FullView({
{chartOptions && (
)}
diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx
index 0af1b9474f..a07a4197e9 100644
--- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx
+++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx
@@ -5,6 +5,7 @@ import cx from 'classnames';
import { ToggleGraphProps } from 'components/Graph/types';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
@@ -47,6 +48,7 @@ function WidgetGraphComponent({
onClickHandler,
onDragSelect,
setGraphVisibility,
+ isFetchingResponse,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [hovered, setHovered] = useState(false);
@@ -222,7 +224,11 @@ function WidgetGraphComponent({
});
};
- if (queryResponse.isLoading || queryResponse.status === 'idle') {
+ const loadingState =
+ (queryResponse.isLoading || queryResponse.status === 'idle') &&
+ widget.panelTypes !== PANEL_TYPES.LIST;
+
+ if (loadingState) {
return (
{queryResponse.isLoading && }
- {queryResponse.isSuccess && (
+ {(queryResponse.isSuccess || widget.panelTypes === PANEL_TYPES.LIST) && (
)}
diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx
index c5dd100a03..d377282cdb 100644
--- a/frontend/src/container/GridCardLayout/GridCard/index.tsx
+++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx
@@ -116,6 +116,12 @@ function GridCardGraph({
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
+ const queryEnabledCondition =
+ isVisible &&
+ !isEmptyWidget &&
+ isQueryEnabled &&
+ widget.panelTypes !== PANEL_TYPES.LIST;
+
const queryResponse = useGetQueryRange(
{
selectedTime: widget?.timePreferance,
@@ -135,7 +141,7 @@ function GridCardGraph({
widget.timePreferance,
],
keepPreviousData: true,
- enabled: isVisible && !isEmptyWidget && isQueryEnabled,
+ enabled: queryEnabledCondition,
refetchOnMount: false,
onError: (error) => {
setErrorMessage(error.message);
@@ -159,7 +165,8 @@ function GridCardGraph({
const isDarkMode = useIsDarkMode();
const menuList =
- widget.panelTypes === PANEL_TYPES.TABLE
+ widget.panelTypes === PANEL_TYPES.TABLE ||
+ widget.panelTypes === PANEL_TYPES.LIST
? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts)
: headerMenuList;
@@ -222,6 +229,7 @@ function GridCardGraph({
onClickHandler={onClickHandler}
graphVisibiltyState={graphVisibility}
setGraphVisibility={setGraphVisibility}
+ isFetchingResponse={queryResponse.isFetching}
/>
)}
diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts
index d71b529207..59711ef9e5 100644
--- a/frontend/src/container/GridCardLayout/GridCard/types.ts
+++ b/frontend/src/container/GridCardLayout/GridCard/types.ts
@@ -30,6 +30,7 @@ export interface WidgetGraphComponentProps extends UplotProps {
isWarning: boolean;
graphVisibiltyState: boolean[];
setGraphVisibility: Dispatch>;
+ isFetchingResponse: boolean;
}
export interface GridCardGraphProps {
diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss
index f65cda37cc..c8b1c72897 100644
--- a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss
+++ b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss
@@ -16,8 +16,28 @@
}
}
+.widget-full-view {
+ .ant-modal-content {
+ background-color: var(--bg-ink-400);
+
+ .ant-modal-header {
+ background-color: var(--bg-ink-400);
+ }
+ }
+}
+
.lightMode {
.fullscreen-grid-container {
background-color: rgb(250, 250, 250);
}
+
+ .widget-full-view {
+ .ant-modal-content {
+ background-color: var(--bg-vanilla-100);
+ }
+
+ .ant-modal-header {
+ background-color: var(--bg-vanilla-100);
+ }
+ }
}
diff --git a/frontend/src/container/GridCardLayout/WidgetHeader/WidgetHeader.styles.scss b/frontend/src/container/GridCardLayout/WidgetHeader/WidgetHeader.styles.scss
index 799f0ed2f2..2fcb3e8e6f 100644
--- a/frontend/src/container/GridCardLayout/WidgetHeader/WidgetHeader.styles.scss
+++ b/frontend/src/container/GridCardLayout/WidgetHeader/WidgetHeader.styles.scss
@@ -2,7 +2,7 @@
display: flex;
justify-content: space-between;
align-items: center;
- height: 40px;
+ height: 30px;
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
diff --git a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx
index 820c3b8a07..63312a5225 100644
--- a/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx
+++ b/frontend/src/container/GridCardLayout/WidgetHeader/index.tsx
@@ -45,6 +45,7 @@ interface IWidgetHeaderProps {
threshold?: ReactNode;
headerMenuList?: MenuItemKeys[];
isWarning: boolean;
+ isFetchingResponse: boolean;
}
function WidgetHeader({
@@ -59,6 +60,7 @@ function WidgetHeader({
threshold,
headerMenuList,
isWarning,
+ isFetchingResponse,
}: IWidgetHeaderProps): JSX.Element | null {
const onEditHandler = useCallback((): void => {
const widgetId = widget.id;
@@ -170,7 +172,7 @@ function WidgetHeader({
{threshold}
- {queryResponse.isFetching && !queryResponse.isError && (
+ {isFetchingResponse && !queryResponse.isError && (
)}
{queryResponse.isError && (
diff --git a/frontend/src/container/GridCardLayout/styles.ts b/frontend/src/container/GridCardLayout/styles.ts
index 273a9a8035..0d79ea0f3b 100644
--- a/frontend/src/container/GridCardLayout/styles.ts
+++ b/frontend/src/container/GridCardLayout/styles.ts
@@ -17,7 +17,7 @@ export const Card = styled(CardComponent)
`
}
.ant-card-body {
- height: calc(100% - 40px);
+ height: calc(100% - 30px);
padding: 0;
}
`;
diff --git a/frontend/src/container/GridPanelSwitch/index.tsx b/frontend/src/container/GridPanelSwitch/index.tsx
index f054fabb5e..09d133f0a0 100644
--- a/frontend/src/container/GridPanelSwitch/index.tsx
+++ b/frontend/src/container/GridPanelSwitch/index.tsx
@@ -1,8 +1,9 @@
import { ToggleGraphProps } from 'components/Graph/types';
-import { PANEL_TYPES_COMPONENT_MAP } from 'constants/panelTypes';
+import { getComponentForPanelType } from 'constants/panelTypes';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GRID_TABLE_CONFIG } from 'container/GridTableComponent/config';
import { FC, forwardRef, memo, useMemo } from 'react';
+import { DataSource } from 'types/common/queryBuilder';
import { GridPanelSwitchProps, PropsTypePropsMap } from './types';
@@ -11,7 +12,19 @@ const GridPanelSwitch = forwardRef<
GridPanelSwitchProps
>(
(
- { panelType, data, yAxisUnit, panelData, query, options, thresholds },
+ {
+ panelType,
+ data,
+ yAxisUnit,
+ panelData,
+ query,
+ options,
+ thresholds,
+ selectedLogFields,
+ selectedTracesFields,
+ dataSource,
+ selectedTime,
+ },
ref,
): JSX.Element | null => {
const currentProps: PropsTypePropsMap = useMemo(() => {
@@ -32,15 +45,38 @@ const GridPanelSwitch = forwardRef<
query,
thresholds,
},
- [PANEL_TYPES.LIST]: null,
+ [PANEL_TYPES.LIST]:
+ dataSource === DataSource.LOGS
+ ? {
+ selectedLogsFields: selectedLogFields || [],
+ query,
+ selectedTime,
+ }
+ : {
+ selectedTracesFields: selectedTracesFields || [],
+ query,
+ selectedTime,
+ },
[PANEL_TYPES.TRACE]: null,
[PANEL_TYPES.EMPTY_WIDGET]: null,
};
return result;
- }, [data, options, ref, yAxisUnit, thresholds, panelData, query]);
+ }, [
+ data,
+ options,
+ ref,
+ yAxisUnit,
+ thresholds,
+ panelData,
+ query,
+ dataSource,
+ selectedLogFields,
+ selectedTime,
+ selectedTracesFields,
+ ]);
- const Component = PANEL_TYPES_COMPONENT_MAP[panelType] as FC<
+ const Component = getComponentForPanelType(panelType, dataSource) as FC<
PropsTypePropsMap[typeof panelType]
>;
const componentProps = useMemo(() => currentProps[panelType], [
diff --git a/frontend/src/container/GridPanelSwitch/types.ts b/frontend/src/container/GridPanelSwitch/types.ts
index df09d4b59d..8f8f0a2a1d 100644
--- a/frontend/src/container/GridPanelSwitch/types.ts
+++ b/frontend/src/container/GridPanelSwitch/types.ts
@@ -2,11 +2,15 @@ import { StaticLineProps, ToggleGraphProps } from 'components/Graph/types';
import { UplotProps } from 'components/Uplot/Uplot';
import { GridTableComponentProps } from 'container/GridTableComponent/types';
import { GridValueComponentProps } from 'container/GridValueComponent/types';
+import { LogsPanelComponentProps } from 'container/LogsPanelTable/LogsPanelComponent';
+import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
+import { TracesTableComponentProps } from 'container/TracesTableComponent/TracesTableComponent';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { ForwardedRef } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryDataV3 } from 'types/api/widgets/getQuery';
+import { DataSource } from 'types/common/queryBuilder';
import uPlot from 'uplot';
import { PANEL_TYPES } from '../../constants/queryBuilder';
@@ -23,6 +27,10 @@ export type GridPanelSwitchProps = {
panelData: QueryDataV3[];
query: Query;
thresholds?: Widgets['thresholds'];
+ dataSource?: DataSource;
+ selectedLogFields?: Widgets['selectedLogFields'];
+ selectedTracesFields?: Widgets['selectedTracesFields'];
+ selectedTime?: timePreferance;
};
export type PropsTypePropsMap = {
@@ -32,6 +40,6 @@ export type PropsTypePropsMap = {
[PANEL_TYPES.VALUE]: GridValueComponentProps;
[PANEL_TYPES.TABLE]: GridTableComponentProps;
[PANEL_TYPES.TRACE]: null;
- [PANEL_TYPES.LIST]: null;
+ [PANEL_TYPES.LIST]: LogsPanelComponentProps | TracesTableComponentProps;
[PANEL_TYPES.EMPTY_WIDGET]: null;
};
diff --git a/frontend/src/container/LogDetailedView/Overview.tsx b/frontend/src/container/LogDetailedView/Overview.tsx
index b25423b4cb..ff2a22fa09 100644
--- a/frontend/src/container/LogDetailedView/Overview.tsx
+++ b/frontend/src/container/LogDetailedView/Overview.tsx
@@ -22,6 +22,7 @@ import TableView from './TableView';
interface OverviewProps {
logData: ILog;
+ isListViewPanel?: boolean;
}
type Props = OverviewProps &
@@ -32,6 +33,7 @@ function Overview({
logData,
onAddToQuery,
onClickActionItem,
+ isListViewPanel = false,
}: Props): JSX.Element {
const [isWrapWord, setIsWrapWord] = useState(false);
const [isSearchVisible, setIsSearchVisible] = useState(false);
@@ -199,6 +201,7 @@ function Overview({
onAddToQuery={onAddToQuery}
fieldSearchInput={fieldSearchInput}
onClickActionItem={onClickActionItem}
+ isListViewPanel={isListViewPanel}
/>
>
),
@@ -210,4 +213,8 @@ function Overview({
);
}
+Overview.defaultProps = {
+ isListViewPanel: false,
+};
+
export default Overview;
diff --git a/frontend/src/container/LogDetailedView/TableView.tsx b/frontend/src/container/LogDetailedView/TableView.tsx
index 21774291f5..2095051fc7 100644
--- a/frontend/src/container/LogDetailedView/TableView.tsx
+++ b/frontend/src/container/LogDetailedView/TableView.tsx
@@ -40,6 +40,7 @@ const RESTRICTED_FIELDS = ['timestamp'];
interface TableViewProps {
logData: ILog;
fieldSearchInput: string;
+ isListViewPanel?: boolean;
}
type Props = TableViewProps &
@@ -51,6 +52,7 @@ function TableView({
fieldSearchInput,
onAddToQuery,
onClickActionItem,
+ isListViewPanel = false,
}: Props): JSX.Element | null {
const dispatch = useDispatch>();
const [isfilterInLoading, setIsFilterInLoading] = useState(false);
@@ -218,38 +220,45 @@ function TableView({
{removeEscapeCharacters(fieldData.value)}
-
-
-
- ) : (
-
- )
- }
- onClick={onClickHandler(OPERATORS.IN, fieldFilterKey, fieldData.value)}
- />
-
-
-
- ) : (
-
- )
- }
- onClick={onClickHandler(
- OPERATORS.NIN,
- fieldFilterKey,
- fieldData.value,
- )}
- />
-
-
+
+ {!isListViewPanel && (
+
+
+
+ ) : (
+
+ )
+ }
+ onClick={onClickHandler(
+ OPERATORS.IN,
+ fieldFilterKey,
+ fieldData.value,
+ )}
+ />
+
+
+
+ ) : (
+
+ )
+ }
+ onClick={onClickHandler(
+ OPERATORS.NIN,
+ fieldFilterKey,
+ fieldData.value,
+ )}
+ />
+
+
+ )}
);
},
@@ -268,6 +277,10 @@ function TableView({
);
}
+TableView.defaultProps = {
+ isListViewPanel: false,
+};
+
interface DataType {
key: string;
field: string;
diff --git a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
index e1e7ca514d..b6d30f23ef 100644
--- a/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
+++ b/frontend/src/container/LogsExplorerList/InfinityTableView/index.tsx
@@ -68,6 +68,7 @@ const InfinityTable = forwardRef(
activeLog,
activeContextLog,
});
+
const { draggedColumns, onDragColumns } = useDragColumns<
Record
>(LOCALSTORAGE.LOGS_LIST_COLUMNS);
diff --git a/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss b/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss
new file mode 100644
index 0000000000..96319b3ae2
--- /dev/null
+++ b/frontend/src/container/LogsPanelTable/LogsPanelComponent.styles.scss
@@ -0,0 +1,80 @@
+.logs-table {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+
+ .resize-table {
+ height: calc(92% - 5px);
+ overflow: scroll;
+
+ .ant-table-wrapper .ant-table-tbody >tr >td {
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 18px;
+ font-family: Inter;
+ .ant-typography {
+ background-color: transparent;
+ color: var(--bg-vanilla-100);
+ margin-bottom: 0;
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 18px;
+ font-family: Inter;
+ }
+ padding: 14px 8px;
+ border: none;
+ cursor: pointer;
+ }
+
+ .ant-table-wrapper .ant-table-header {
+ border-bottom: 0.5px solid var(--bg-slate-400);
+ }
+
+ .ant-table-wrapper .ant-table-thead > tr > th {
+ font-family: Inter;
+ color: var(--bg-vanilla-100);
+ background-color: transparent;
+ border: none;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 600;
+ line-height: 22px;
+ letter-spacing: 0.5px;
+ padding: 8px;
+ }
+
+ .ant-table-wrapper .ant-table-thead > tr > th::before {
+ display: none;
+ }
+ }
+
+ .controller {
+ position: absolute;
+ bottom: 5px;
+ right: 10px;
+ }
+}
+
+.lightMode {
+ .logs-table {
+ .resize-table {
+ .ant-table-wrapper .ant-table-tbody >tr >td {
+ background-color: var(--bg-vanilla-100);
+ .ant-typography {
+ color: var(--bg-ink-500);
+ }
+ }
+ .ant-table-wrapper .ant-table-thead > tr > th {
+ background-color: var(--bg-vanilla-100);
+ color: var(--bg-ink-500);
+ }
+
+ .ant-table-wrapper .ant-table-header {
+ border-bottom: 0.5px solid var(--bg-vanilla-400);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/container/LogsPanelTable/LogsPanelComponent.tsx b/frontend/src/container/LogsPanelTable/LogsPanelComponent.tsx
new file mode 100644
index 0000000000..64be38c035
--- /dev/null
+++ b/frontend/src/container/LogsPanelTable/LogsPanelComponent.tsx
@@ -0,0 +1,311 @@
+import './LogsPanelComponent.styles.scss';
+
+import { Table } from 'antd';
+import LogDetail from 'components/LogDetail';
+import { VIEW_TYPES } from 'components/LogDetail/constants';
+import { SOMETHING_WENT_WRONG } from 'constants/api';
+import { OPERATORS, PANEL_TYPES } from 'constants/queryBuilder';
+import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
+import Controls from 'container/Controls';
+import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
+import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
+import { tableStyles } from 'container/TracesExplorer/ListView/styles';
+import { useActiveLog } from 'hooks/logs/useActiveLog';
+import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
+import { Pagination } from 'hooks/queryPagination';
+import { useLogsData } from 'hooks/useLogsData';
+import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
+import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
+import { FlatLogData } from 'lib/logs/flatLogData';
+import { RowData } from 'lib/query/createTableColumnsFromQuery';
+import { useDashboard } from 'providers/Dashboard/Dashboard';
+import {
+ HTMLAttributes,
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
+import { useSelector } from 'react-redux';
+import { AppState } from 'store/reducers';
+import { Widgets } from 'types/api/dashboard/getAll';
+import { ILog } from 'types/api/logs/log';
+import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { Query } from 'types/api/queryBuilder/queryBuilderData';
+import { GlobalReducer } from 'types/reducer/globalTime';
+import { v4 as uuid } from 'uuid';
+
+import { getLogPanelColumnsList } from './utils';
+
+function LogsPanelComponent({
+ selectedLogsFields,
+ query,
+ selectedTime,
+}: LogsPanelComponentProps): JSX.Element {
+ const { selectedTime: globalSelectedTime, maxTime, minTime } = useSelector<
+ AppState,
+ GlobalReducer
+ >((state) => state.globalTime);
+
+ const [pagination, setPagination] = useState({
+ offset: 0,
+ limit: query.builder.queryData[0].limit || 0,
+ });
+
+ const [requestData, setRequestData] = useState(() => {
+ const updatedQuery = { ...query };
+ updatedQuery.builder.queryData[0].pageSize = 10;
+ return {
+ query: updatedQuery,
+ graphType: PANEL_TYPES.LIST,
+ selectedTime: 'GLOBAL_TIME',
+ globalSelectedInterval: globalSelectedTime,
+ tableParams: {
+ pagination,
+ },
+ };
+ });
+
+ useEffect(() => {
+ setRequestData({
+ ...requestData,
+ globalSelectedInterval: globalSelectedTime,
+ tableParams: {
+ pagination,
+ },
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [pagination]);
+
+ const [pageSize, setPageSize] = useState(10);
+ const { selectedDashboard } = useDashboard();
+
+ const handleChangePageSize = (value: number): void => {
+ setPagination({
+ ...pagination,
+ limit: 0,
+ offset: value,
+ });
+ setPageSize(value);
+ const newQueryData = { ...requestData.query };
+ newQueryData.builder.queryData[0].pageSize = value;
+ const newRequestData = {
+ ...requestData,
+ query: newQueryData,
+ tableParams: {
+ pagination,
+ },
+ };
+ setRequestData(newRequestData);
+ };
+
+ const { data, isFetching, isError } = useGetQueryRange(
+ {
+ ...requestData,
+ globalSelectedInterval: globalSelectedTime,
+ selectedTime: selectedTime?.enum || 'GLOBAL_TIME',
+ variables: getDashboardVariables(selectedDashboard?.data.variables),
+ },
+ {
+ queryKey: [
+ REACT_QUERY_KEY.GET_QUERY_RANGE,
+ globalSelectedTime,
+ maxTime,
+ minTime,
+ requestData,
+ pagination,
+ selectedDashboard?.data.variables,
+ ],
+ enabled: !!requestData.query && !!selectedLogsFields?.length,
+ },
+ );
+
+ const columns = getLogPanelColumnsList(selectedLogsFields);
+
+ const dataLength =
+ data?.payload?.data?.newResult?.data?.result[0]?.list?.length;
+ const totalCount = useMemo(() => dataLength || 0, [dataLength]);
+
+ const [firstLog, setFirstLog] = useState();
+ const [lastLog, setLastLog] = useState();
+
+ const { logs } = useLogsData({
+ result: data?.payload.data.newResult.data.result,
+ panelType: PANEL_TYPES.LIST,
+ stagedQuery: query,
+ });
+
+ useEffect(() => {
+ if (logs.length) {
+ setFirstLog(logs[0]);
+ setLastLog(logs[logs.length - 1]);
+ }
+ }, [logs]);
+
+ const flattenLogData = useMemo(
+ () => logs.map((log) => FlatLogData(log) as RowData),
+ [logs],
+ );
+
+ const {
+ activeLog,
+ onSetActiveLog,
+ onClearActiveLog,
+ onAddToQuery,
+ } = useActiveLog();
+
+ const handleRow = useCallback(
+ (record: RowData): HTMLAttributes => ({
+ onClick: (): void => {
+ const log = logs.find((item) => item.id === record.id);
+ if (log) onSetActiveLog(log);
+ },
+ }),
+ [logs, onSetActiveLog],
+ );
+
+ const isOrderByTimeStamp =
+ query.builder.queryData[0].orderBy.length > 0 &&
+ query.builder.queryData[0].orderBy[0].columnName === 'timestamp';
+
+ const handlePreviousPagination = (): void => {
+ if (isOrderByTimeStamp) {
+ setRequestData({
+ ...requestData,
+ query: {
+ ...requestData.query,
+ builder: {
+ ...requestData.query.builder,
+ queryData: [
+ {
+ ...requestData.query.builder.queryData[0],
+ filters: {
+ ...requestData.query.builder.queryData[0].filters,
+ items: [
+ {
+ id: uuid(),
+ key: {
+ key: 'id',
+ type: '',
+ dataType: DataTypes.String,
+ isColumn: true,
+ },
+ op: OPERATORS['>'],
+ value: firstLog?.id || '',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ return;
+ }
+ setPagination({
+ ...pagination,
+ limit: 0,
+ offset: pagination.offset - pageSize,
+ });
+ };
+
+ const handleNextPagination = (): void => {
+ if (isOrderByTimeStamp) {
+ setRequestData({
+ ...requestData,
+ query: {
+ ...requestData.query,
+ builder: {
+ ...requestData.query.builder,
+ queryData: [
+ {
+ ...requestData.query.builder.queryData[0],
+ filters: {
+ ...requestData.query.builder.queryData[0].filters,
+ items: [
+ {
+ id: uuid(),
+ key: {
+ key: 'id',
+ type: '',
+ dataType: DataTypes.String,
+ isColumn: true,
+ },
+ op: OPERATORS['<'],
+ value: lastLog?.id || '',
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ });
+ return;
+ }
+ setPagination({
+ ...pagination,
+ limit: 0,
+ offset: pagination.offset + pageSize,
+ });
+ };
+
+ if (isError) {
+ return {SOMETHING_WENT_WRONG}
;
+ }
+
+ return (
+ <>
+
+
+ {!query.builder.queryData[0].limit && (
+
+
+
+ )}
+
+
+ >
+ );
+}
+
+export type LogsPanelComponentProps = {
+ selectedLogsFields: Widgets['selectedLogFields'];
+ query: Query;
+ selectedTime?: timePreferance;
+};
+
+LogsPanelComponent.defaultProps = {
+ selectedTime: undefined,
+};
+
+export default LogsPanelComponent;
diff --git a/frontend/src/container/LogsPanelTable/utils.tsx b/frontend/src/container/LogsPanelTable/utils.tsx
new file mode 100644
index 0000000000..46701e763b
--- /dev/null
+++ b/frontend/src/container/LogsPanelTable/utils.tsx
@@ -0,0 +1,38 @@
+import { ColumnsType } from 'antd/es/table';
+import { Typography } from 'antd/lib';
+// import Typography from 'antd/es/typography/Typography';
+import { RowData } from 'lib/query/createTableColumnsFromQuery';
+import { ReactNode } from 'react';
+import { Widgets } from 'types/api/dashboard/getAll';
+import { IField } from 'types/api/logs/fields';
+
+export const getLogPanelColumnsList = (
+ selectedLogFields: Widgets['selectedLogFields'],
+): ColumnsType => {
+ const initialColumns: ColumnsType = [];
+
+ const columns: ColumnsType =
+ selectedLogFields?.map((field: IField) => {
+ const { name } = field;
+ return {
+ title: name,
+ dataIndex: name,
+ key: name,
+ width: name === 'body' ? 350 : 100,
+ render: (value: ReactNode): JSX.Element => {
+ if (name === 'body') {
+ return (
+
+ {value}
+
+ );
+ }
+
+ return {value};
+ },
+ responsive: ['md'],
+ };
+ }) || [];
+
+ return [...initialColumns, ...columns];
+};
diff --git a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
index 20becbb810..b974972d70 100644
--- a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
+++ b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts
@@ -22,4 +22,6 @@ export const getWidgetQueryBuilder = ({
yAxisUnit,
softMax: null,
softMin: null,
+ selectedLogFields: [],
+ selectedTracesFields: [],
});
diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts b/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts
new file mode 100644
index 0000000000..44512e3a00
--- /dev/null
+++ b/frontend/src/container/NewDashboard/ComponentsSlider/constants.ts
@@ -0,0 +1,88 @@
+import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
+import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { Query } from 'types/api/queryBuilder/queryBuilderData';
+import { LogsAggregatorOperator } from 'types/common/queryBuilder';
+
+export const PANEL_TYPES_INITIAL_QUERY = {
+ [PANEL_TYPES.TIME_SERIES]: initialQueriesMap.metrics,
+ [PANEL_TYPES.VALUE]: initialQueriesMap.metrics,
+ [PANEL_TYPES.TABLE]: initialQueriesMap.metrics,
+ [PANEL_TYPES.LIST]: initialQueriesMap.logs,
+ [PANEL_TYPES.TRACE]: initialQueriesMap.traces,
+ [PANEL_TYPES.EMPTY_WIDGET]: initialQueriesMap.metrics,
+};
+
+export const listViewInitialLogQuery: Query = {
+ ...initialQueriesMap.logs,
+ builder: {
+ ...initialQueriesMap.logs.builder,
+ queryData: [
+ {
+ ...initialQueriesMap.logs.builder.queryData[0],
+ aggregateOperator: LogsAggregatorOperator.NOOP,
+ orderBy: [{ columnName: 'timestamp', order: 'desc' }],
+ offset: 0,
+ pageSize: 100,
+ },
+ ],
+ },
+};
+
+export const listViewInitialTraceQuery = {
+ // it should be the above commented query
+ ...initialQueriesMap.traces,
+ builder: {
+ ...initialQueriesMap.traces.builder,
+ queryData: [
+ {
+ ...initialQueriesMap.traces.builder.queryData[0],
+ aggregateOperator: LogsAggregatorOperator.NOOP,
+ orderBy: [{ columnName: 'timestamp', order: 'desc' }],
+ offset: 0,
+ pageSize: 10,
+ selectColumns: [
+ {
+ key: 'serviceName',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'serviceName--string--tag--true',
+ },
+ {
+ key: 'name',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'name--string--tag--true',
+ },
+ {
+ key: 'durationNano',
+ dataType: 'float64',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'durationNano--float64--tag--true',
+ },
+ {
+ key: 'httpMethod',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'httpMethod--string--tag--true',
+ },
+ {
+ key: 'responseStatusCode',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'responseStatusCode--string--tag--true',
+ },
+ ] as BaseAutocompleteData[],
+ },
+ ],
+ },
+};
diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
index d355edfd1a..80f1e745da 100644
--- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
+++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx
@@ -1,6 +1,6 @@
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { QueryParams } from 'constants/query';
-import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
@@ -8,8 +8,14 @@ import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { CSSProperties } from 'react';
+import { LogsAggregatorOperator } from 'types/common/queryBuilder';
import { v4 as uuid } from 'uuid';
+import {
+ listViewInitialLogQuery,
+ listViewInitialTraceQuery,
+ PANEL_TYPES_INITIAL_QUERY,
+} from './constants';
import menuItems from './menuItems';
import { Card, Container, Text } from './styles';
@@ -28,6 +34,7 @@ function DashboardGraphSlider(): JSX.Element {
const updateDashboardMutation = useUpdateDashboard();
+ // eslint-disable-next-line sonarjs/cognitive-complexity
const onClickHandler = (name: PANEL_TYPES) => (): void => {
const id = uuid();
@@ -61,10 +68,28 @@ function DashboardGraphSlider(): JSX.Element {
nullZeroValues: '',
opacity: '',
panelTypes: name,
- query: initialQueriesMap.metrics,
+ query:
+ name === PANEL_TYPES.LIST
+ ? listViewInitialLogQuery
+ : PANEL_TYPES_INITIAL_QUERY[name],
timePreferance: 'GLOBAL_TIME',
softMax: null,
softMin: null,
+ selectedLogFields: [
+ {
+ dataType: 'string',
+ type: '',
+ name: 'body',
+ },
+ {
+ dataType: 'string',
+ type: '',
+ name: 'timestamp',
+ },
+ ],
+ selectedTracesFields: [
+ ...listViewInitialTraceQuery.builder.queryData[0].selectColumns,
+ ],
},
],
},
@@ -73,16 +98,43 @@ function DashboardGraphSlider(): JSX.Element {
onSuccess: (data) => {
if (data.payload) {
handleToggleDashboardSlider(false);
+ const queryParamsLog = {
+ graphType: name,
+ widgetId: id,
+ [QueryParams.compositeQuery]: JSON.stringify({
+ ...PANEL_TYPES_INITIAL_QUERY[name],
+ builder: {
+ ...PANEL_TYPES_INITIAL_QUERY[name].builder,
+ queryData: [
+ {
+ ...PANEL_TYPES_INITIAL_QUERY[name].builder.queryData[0],
+ aggregateOperator: LogsAggregatorOperator.NOOP,
+ orderBy: [{ columnName: 'timestamp', order: 'desc' }],
+ offset: 0,
+ pageSize: 100,
+ },
+ ],
+ },
+ }),
+ };
const queryParams = {
graphType: name,
widgetId: id,
- [QueryParams.compositeQuery]: JSON.stringify(initialQueriesMap.metrics),
+ [QueryParams.compositeQuery]: JSON.stringify(
+ PANEL_TYPES_INITIAL_QUERY[name],
+ ),
};
- history.push(
- `${history.location.pathname}/new?${createQueryParams(queryParams)}`,
- );
+ if (name === PANEL_TYPES.LIST) {
+ history.push(
+ `${history.location.pathname}/new?${createQueryParams(queryParamsLog)}`,
+ );
+ } else {
+ history.push(
+ `${history.location.pathname}/new?${createQueryParams(queryParams)}`,
+ );
+ }
}
},
onError: () => {
diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
index 560da8deef..1aaa3a71ea 100644
--- a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
+++ b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts
@@ -1,3 +1,4 @@
+import List from 'assets/Dashboard/List';
import TableIcon from 'assets/Dashboard/Table';
import TimeSeriesIcon from 'assets/Dashboard/TimeSeries';
import ValueIcon from 'assets/Dashboard/Value';
@@ -16,6 +17,7 @@ const Items: ItemsProps[] = [
display: 'Value',
},
{ name: PANEL_TYPES.TABLE, Icon: TableIcon, display: 'Table' },
+ { name: PANEL_TYPES.LIST, Icon: List, display: 'List' },
];
interface ItemsProps {
diff --git a/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.styles.scss b/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.styles.scss
new file mode 100644
index 0000000000..f9652e859a
--- /dev/null
+++ b/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.styles.scss
@@ -0,0 +1,184 @@
+.explorer-columns-renderer {
+ margin-top: 10px;
+
+ .title {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
+
+ .ant-typography {
+ color: var(rgba(255, 255, 255, 0.85));
+ font-family: "Inter";
+ font-size: 13px;
+ font-style: normal;
+ font-weight: 400;
+ line-height: 22px;
+ letter-spacing: 0.5px;
+ }
+
+ .ant-divider {
+ margin: 8px 0 !important;
+ border: 0.5px solid var(--bg-slate-400);
+ }
+
+ .explorer-columns-contents {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ .explorer-columns {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ overflow-x: scroll;
+ min-width: 90%;
+
+ .explorer-columns-list {
+ display: flex !important;
+ }
+
+ .explorer-column-card {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px;
+ min-width: 200px;
+ border-radius: 2px;
+ border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
+ background: var(--bg-slate-500);
+ cursor: unset;
+
+ .explorer-column-title {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-family: Inter;
+ font-size: 12px;
+ cursor: grab;
+ }
+
+ .lucide-trash2 {
+ cursor: pointer !important;
+ }
+
+ }
+ }
+
+ .explorer-columns::-webkit-scrollbar {
+ height: 0px; /* Height of the scrollbar */
+ }
+
+ .action-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ padding: 0px 16px;
+ border-radius: 2px;
+ background: var(--bg-robin-400);
+ }
+ }
+}
+
+.explorer-columns-search {
+ border: 1px solid rgba(118, 136, 201, 0.12);
+ border-radius: 6px;
+ padding: 0px;
+ background:#141414;
+ > input {
+ height: 32px;
+ padding: 0 6px;
+ }
+
+
+}
+
+.explorer-columns-dropdown {
+ height: 200px;
+ background-color: var(--bg-slate-500);
+ overflow: hidden !important;
+ .ant-dropdown-menu {
+ padding: 0;
+
+ .ant-dropdown-menu-item {
+ padding: 4px;
+ .ant-checkbox-wrapper {
+ padding: 2px 8px !important;
+ }
+
+ .attribute-columns {
+ display: flex;
+ flex-direction: column;
+ height: 160px;
+ overflow: scroll;
+ }
+
+ .attribute-columns::-webkit-scrollbar {
+ width: 3px; /* Width of the scrollbar */
+ }
+
+ .attribute-columns::-webkit-scrollbar-track {
+ background: var(--bg-slate-500); /* Color of the track */
+ }
+
+ .attribute-columns::-webkit-scrollbar-thumb {
+ background: var(--bg-vanilla-400); /* Color of the thumb */
+ border-radius: 4px; /* Roundness of the thumb */
+ }
+
+ .attribute-columns::-webkit-scrollbar-thumb:hover {
+ background: var(--bg-vanilla-300); /* Color of the thumb on hover */
+ }
+ }
+ }
+}
+
+.lightMode {
+ .explorer-columns-renderer {
+
+ .ant-divider {
+ border: 0.5px solid var(--bg-vanilla-300);
+ }
+
+ .explorer-columns {
+ .explorer-column-card {
+ border: 1px solid var(--colorBorder, rgba(118, 136, 201, 0.12));
+ background: var(--bg-vanilla-200);
+ }
+ }
+
+ .explorer-columns-search {
+ border: 1px solid rgba(118, 136, 201, 0.12);
+ }
+ }
+
+ .explorer-columns-dropdown {
+ background-color: var(--bg-vanilla-100);
+
+ .ant-dropdown-menu-item {
+ .attribute-columns {
+ &::-webkit-scrollbar {
+ width: 3px; /* Width of the scrollbar */
+ }
+
+ &::-webkit-scrollbar-track {
+ background: var(--bg-vanilla-200); /* Color of the track */
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: var(--bg-vanilla-400); /* Color of the thumb */
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background: var(--bg-vanilla-300); /* Color of the thumb on hover */
+ }
+ }
+ }
+ }
+
+ .explorer-columns-search {
+ background: var(--bg-vanilla-100);
+ }
+}
diff --git a/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.tsx b/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.tsx
new file mode 100644
index 0000000000..a9a8d9ceb2
--- /dev/null
+++ b/frontend/src/container/NewWidget/LeftContainer/ExplorerColumnsRenderer.tsx
@@ -0,0 +1,328 @@
+/* eslint-disable sonarjs/cognitive-complexity */
+/* eslint-disable react/jsx-props-no-spreading */
+import './ExplorerColumnsRenderer.styles.scss';
+
+import { Color } from '@signozhq/design-tokens';
+import {
+ Button,
+ Checkbox,
+ Divider,
+ Dropdown,
+ Input,
+ Tooltip,
+ Typography,
+} from 'antd';
+import { MenuProps } from 'antd/lib';
+import Spinner from 'components/Spinner';
+import { SOMETHING_WENT_WRONG } from 'constants/api';
+import { useGetAggregateKeys } from 'hooks/queryBuilder/useGetAggregateKeys';
+import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
+import { useIsDarkMode } from 'hooks/useDarkMode';
+import {
+ AlertCircle,
+ GripVertical,
+ PlusCircle,
+ Search,
+ Trash2,
+} from 'lucide-react';
+import { useState } from 'react';
+import {
+ DragDropContext,
+ Draggable,
+ Droppable,
+ DropResult,
+} from 'react-beautiful-dnd';
+import { DataSource } from 'types/common/queryBuilder';
+
+import { WidgetGraphProps } from '../types';
+
+type LogColumnsRendererProps = {
+ setSelectedLogFields: WidgetGraphProps['setSelectedLogFields'];
+ selectedLogFields: WidgetGraphProps['selectedLogFields'];
+ selectedTracesFields: WidgetGraphProps['selectedTracesFields'];
+ setSelectedTracesFields: WidgetGraphProps['setSelectedTracesFields'];
+};
+
+function ExplorerColumnsRenderer({
+ selectedLogFields,
+ setSelectedLogFields,
+ selectedTracesFields,
+ setSelectedTracesFields,
+}: LogColumnsRendererProps): JSX.Element {
+ const { currentQuery } = useQueryBuilder();
+ const [searchText, setSearchText] = useState('');
+ const [open, setOpen] = useState(false);
+
+ const initialDataSource = currentQuery.builder.queryData[0].dataSource;
+
+ const { data, isLoading, isError } = useGetAggregateKeys(
+ {
+ aggregateAttribute: '',
+ dataSource: currentQuery.builder.queryData[0].dataSource,
+ aggregateOperator: currentQuery.builder.queryData[0].aggregateOperator,
+ searchText: '',
+ tagType: '',
+ },
+ {
+ queryKey: [
+ currentQuery.builder.queryData[0].dataSource,
+ currentQuery.builder.queryData[0].aggregateOperator,
+ ],
+ },
+ );
+
+ const isAttributeKeySelected = (key: string): boolean => {
+ if (initialDataSource === DataSource.LOGS && selectedLogFields) {
+ return selectedLogFields.some((field) => field.name === key);
+ }
+ if (initialDataSource === DataSource.TRACES && selectedTracesFields) {
+ return selectedTracesFields.some((field) => field.key === key);
+ }
+ return false;
+ };
+
+ const handleCheckboxChange = (key: string): void => {
+ if (
+ initialDataSource === DataSource.LOGS &&
+ setSelectedLogFields !== undefined
+ ) {
+ if (selectedLogFields) {
+ if (isAttributeKeySelected(key)) {
+ setSelectedLogFields(
+ selectedLogFields.filter((field) => field.name !== key),
+ );
+ } else {
+ setSelectedLogFields([
+ ...selectedLogFields,
+ { dataType: 'string', name: key, type: '' },
+ ]);
+ }
+ } else {
+ setSelectedLogFields([{ dataType: 'string', name: key, type: '' }]);
+ }
+ } else if (
+ initialDataSource === DataSource.TRACES &&
+ setSelectedTracesFields !== undefined
+ ) {
+ const selectedField = data?.payload?.attributeKeys?.find(
+ (attributeKey) => attributeKey.key === key,
+ );
+ if (selectedTracesFields) {
+ if (isAttributeKeySelected(key)) {
+ setSelectedTracesFields(
+ selectedTracesFields.filter((field) => field.key !== key),
+ );
+ } else if (selectedField) {
+ setSelectedTracesFields([...selectedTracesFields, selectedField]);
+ }
+ } else if (selectedField) setSelectedTracesFields([selectedField]);
+ }
+ setOpen(false);
+ };
+
+ const handleSearchChange = (e: React.ChangeEvent): void => {
+ setSearchText(e.target.value);
+ };
+
+ const items: MenuProps['items'] = [
+ {
+ key: 'search',
+ label: (
+ }
+ />
+ ),
+ },
+ {
+ key: 'columns',
+ label: (
+
+ {data?.payload?.attributeKeys
+ ?.filter((attributeKey) =>
+ attributeKey.key.toLowerCase().includes(searchText.toLowerCase()),
+ )
+ ?.map((attributeKey) => (
+ handleCheckboxChange(attributeKey.key)}
+ style={{ padding: 0 }}
+ key={attributeKey.key}
+ >
+ {attributeKey.key}
+
+ ))}
+
+ ),
+ },
+ ];
+
+ const removeSelectedLogField = (name: string): void => {
+ if (
+ initialDataSource === DataSource.LOGS &&
+ setSelectedLogFields &&
+ selectedLogFields
+ ) {
+ setSelectedLogFields(
+ selectedLogFields.filter((field) => field.name !== name),
+ );
+ }
+ if (
+ initialDataSource === DataSource.TRACES &&
+ setSelectedTracesFields &&
+ selectedTracesFields
+ ) {
+ setSelectedTracesFields(
+ selectedTracesFields.filter((field) => field.key !== name),
+ );
+ }
+ };
+
+ const onDragEnd = (result: DropResult): void => {
+ if (!result.destination) {
+ return;
+ }
+
+ if (
+ initialDataSource === DataSource.LOGS &&
+ selectedLogFields &&
+ setSelectedLogFields
+ ) {
+ const items = [...selectedLogFields];
+ const [reorderedItem] = items.splice(result.source.index, 1);
+ items.splice(result.destination.index, 0, reorderedItem);
+
+ setSelectedLogFields(items);
+ }
+ if (
+ initialDataSource === DataSource.TRACES &&
+ selectedTracesFields &&
+ setSelectedTracesFields
+ ) {
+ const items = [...selectedTracesFields];
+ const [reorderedItem] = items.splice(result.source.index, 1);
+ items.splice(result.destination.index, 0, reorderedItem);
+
+ setSelectedTracesFields(items);
+ }
+ };
+
+ const toggleDropdown = (): void => {
+ setOpen(!open);
+ if (!open) {
+ setSearchText('');
+ }
+ };
+
+ const isDarkMode = useIsDarkMode();
+
+ if (isLoading) {
+ return ;
+ }
+
+ return (
+
+
+
Columns
+ {isError && (
+
+
+
+ )}
+
+
+ {!isError && (
+
+
+
+ {(provided): JSX.Element => (
+
+ {initialDataSource === DataSource.LOGS &&
+ selectedLogFields &&
+ selectedLogFields.map((field, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ {(dragProvided): JSX.Element => (
+
+
+
+ {field.name}
+
+
removeSelectedLogField(field.name)}
+ />
+
+ )}
+
+ ))}
+ {initialDataSource === DataSource.TRACES &&
+ selectedTracesFields &&
+ selectedTracesFields.map((field, index) => (
+ // eslint-disable-next-line react/no-array-index-key
+
+ {(dragProvided): JSX.Element => (
+
+
+
+ {field.key}
+
+
removeSelectedLogField(field.key)}
+ />
+
+ )}
+
+ ))}
+
+ )}
+
+
+
+
+
+ }
+ onClick={toggleDropdown}
+ />
+
+
+
+ )}
+
+ );
+}
+
+export default ExplorerColumnsRenderer;
diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
index 9fe20bd8bc..4c66c70b83 100644
--- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx
@@ -18,7 +18,7 @@ import {
getPreviousWidgets,
getSelectedWidgetIndex,
} from 'providers/Dashboard/util';
-import { useCallback, useMemo, useState } from 'react';
+import { useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -35,7 +35,6 @@ function QuerySection({
selectedTime,
}: QueryProps): JSX.Element {
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
- const [currentTab, setCurrentTab] = useState(currentQuery.queryType);
const urlQuery = useUrlQuery();
const { minTime, maxTime } = useSelector(
@@ -100,7 +99,6 @@ function QuerySection({
],
},
});
-
redirectWithQueryBuilderData(updatedQuery);
},
[
@@ -114,11 +112,13 @@ function QuerySection({
);
const handleQueryCategoryChange = (qCategory: string): void => {
- const currentQueryType = qCategory as EQueryType;
- setCurrentTab(qCategory as EQueryType);
+ const currentQueryType = qCategory;
featureResponse.refetch().then(() => {
- handleStageQuery({ ...currentQuery, queryType: currentQueryType });
+ handleStageQuery({
+ ...currentQuery,
+ queryType: currentQueryType as EQueryType,
+ });
});
};
@@ -134,6 +134,27 @@ function QuerySection({
return config;
}, []);
+ const listItems = [
+ {
+ key: EQueryType.QUERY_BUILDER,
+ label: (
+
+
+
+ ),
+ tab: Query Builder,
+ children: (
+
+ ),
+ },
+ ];
+
const items = [
{
key: EQueryType.QUERY_BUILDER,
@@ -180,8 +201,12 @@ function QuerySection({
@@ -197,7 +222,7 @@ function QuerySection({
}
- items={items}
+ items={selectedGraph === PANEL_TYPES.LIST ? listItems : items}
/>
);
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
index 219d4e011b..99da2e517e 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx
@@ -10,7 +10,7 @@ interface IPlotTagProps {
}
function PlotTag({ queryType, panelType }: IPlotTagProps): JSX.Element | null {
- if (queryType === undefined) {
+ if (queryType === undefined || panelType === PANEL_TYPES.LIST) {
return null;
}
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx
index c3d47880d3..2472d94092 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphContainer.tsx
@@ -1,5 +1,6 @@
import { Card, Typography } from 'antd';
import Spinner from 'components/Spinner';
+import { PANEL_TYPES } from 'constants/queryBuilder';
import { WidgetGraphProps } from 'container/NewWidget/types';
import { useGetWidgetQueryRange } from 'hooks/queryBuilder/useGetWidgetQueryRange';
import useUrlQuery from 'hooks/useUrlQuery';
@@ -16,6 +17,8 @@ function WidgetGraphContainer({
fillSpans = false,
softMax,
softMin,
+ selectedLogFields,
+ selectedTracesFields,
}: WidgetGraphProps): JSX.Element {
const { selectedDashboard } = useDashboard();
@@ -46,7 +49,21 @@ function WidgetGraphContainer({
if (getWidgetQueryRange.isLoading) {
return ;
}
- if (getWidgetQueryRange.data?.payload.data.result.length === 0) {
+
+ if (
+ selectedGraph !== PANEL_TYPES.LIST &&
+ getWidgetQueryRange.data?.payload.data.result.length === 0
+ ) {
+ return (
+
+ No Data
+
+ );
+ }
+ if (
+ selectedGraph === PANEL_TYPES.LIST &&
+ getWidgetQueryRange.data?.payload.data.newResult.data.result.length === 0
+ ) {
return (
No Data
@@ -63,6 +80,9 @@ function WidgetGraphContainer({
fillSpans={fillSpans}
softMax={softMax}
softMin={softMin}
+ selectedLogFields={selectedLogFields}
+ selectedTracesFields={selectedTracesFields}
+ selectedTime={selectedTime}
/>
);
}
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx
index f4a8ed1b22..ccd0a91ea3 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraphs.tsx
@@ -1,6 +1,7 @@
import { QueryParams } from 'constants/query';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
+import { timePreferance } from 'container/NewWidget/RightContainer/timeItems';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
@@ -30,8 +31,11 @@ function WidgetGraph({
fillSpans,
softMax,
softMin,
+ selectedLogFields,
+ selectedTracesFields,
+ selectedTime,
}: WidgetGraphProps): JSX.Element {
- const { stagedQuery } = useQueryBuilder();
+ const { stagedQuery, currentQuery } = useQueryBuilder();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
@@ -156,6 +160,10 @@ function WidgetGraph({
}
query={stagedQuery || selectedWidget.query}
thresholds={thresholds}
+ selectedLogFields={selectedLogFields}
+ selectedTracesFields={selectedTracesFields}
+ dataSource={currentQuery.builder.queryData[0].dataSource}
+ selectedTime={selectedTime}
/>
);
@@ -172,6 +180,9 @@ interface WidgetGraphProps {
>;
softMax: number | null;
softMin: number | null;
+ selectedLogFields: Widgets['selectedLogFields'];
+ selectedTracesFields: Widgets['selectedTracesFields'];
+ selectedTime: timePreferance;
}
export default WidgetGraph;
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
index 56846c8dec..1f306a41a2 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/index.tsx
@@ -19,6 +19,8 @@ function WidgetGraph({
fillSpans,
softMax,
softMin,
+ selectedLogFields,
+ selectedTracesFields,
}: WidgetGraphProps): JSX.Element {
const { currentQuery } = useQueryBuilder();
const { selectedDashboard } = useDashboard();
@@ -57,6 +59,8 @@ function WidgetGraph({
fillSpans={fillSpans}
softMax={softMax}
softMin={softMin}
+ selectedLogFields={selectedLogFields}
+ selectedTracesFields={selectedTracesFields}
/>
);
diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts
index b1bf36b588..a5d030e27c 100644
--- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts
+++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts
@@ -13,8 +13,10 @@ export const Container = styled(Card)