mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 05:15:57 +08:00
feat: added new and cloned panel at the bottom of the page (#6993)
* feat: added new and cloned panel at the bottom of the page * feat: added common util and took possible space available in last row in account * feat: added changes for empty layout * feat: added different test cases * feat: remove console.log * feat: added default value to widgetWidth
This commit is contained in:
parent
9a75e27ec3
commit
02c2b55d5e
@ -6,6 +6,7 @@ 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 { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
|
||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
@ -133,18 +134,14 @@ function WidgetGraphComponent({
|
||||
(l) => l.i === widget.id,
|
||||
);
|
||||
|
||||
// added the cloned panel on the top as it is given most priority when arranging
|
||||
// in the layout. React_grid_layout assigns priority from top, hence no random position for cloned panel
|
||||
const layout = [
|
||||
{
|
||||
i: uuid,
|
||||
w: originalPanelLayout?.w || 6,
|
||||
x: 0,
|
||||
h: originalPanelLayout?.h || 6,
|
||||
y: 0,
|
||||
},
|
||||
...(selectedDashboard.data.layout || []),
|
||||
];
|
||||
const newLayoutItem = placeWidgetAtBottom(
|
||||
uuid,
|
||||
selectedDashboard?.data.layout || [],
|
||||
originalPanelLayout?.w || 6,
|
||||
originalPanelLayout?.h || 6,
|
||||
);
|
||||
|
||||
const layout = [...(selectedDashboard.data.layout || []), newLayoutItem];
|
||||
|
||||
updateDashboardMutation.mutateAsync(
|
||||
{
|
||||
|
92
frontend/src/container/NewWidget/__test__/NewWidget.test.tsx
Normal file
92
frontend/src/container/NewWidget/__test__/NewWidget.test.tsx
Normal file
@ -0,0 +1,92 @@
|
||||
// This test suite covers several important scenarios:
|
||||
// - Empty layout - widget should be placed at origin (0,0)
|
||||
// - Empty layout with custom dimensions
|
||||
// - Placing widget next to an existing widget when there's space in the last row
|
||||
// - Placing widget at bottom when the last row is full
|
||||
// - Handling multiple rows correctly
|
||||
// - Handling widgets with different heights
|
||||
|
||||
import { placeWidgetAtBottom } from '../utils';
|
||||
|
||||
describe('placeWidgetAtBottom', () => {
|
||||
it('should place widget at (0,0) when layout is empty', () => {
|
||||
const result = placeWidgetAtBottom('widget1', []);
|
||||
expect(result).toEqual({
|
||||
i: 'widget1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('should place widget at (0,0) with custom dimensions when layout is empty', () => {
|
||||
const result = placeWidgetAtBottom('widget1', [], 4, 8);
|
||||
expect(result).toEqual({
|
||||
i: 'widget1',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 4,
|
||||
h: 8,
|
||||
});
|
||||
});
|
||||
|
||||
it('should place widget next to existing widget in last row if space available', () => {
|
||||
const existingLayout = [{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 }];
|
||||
const result = placeWidgetAtBottom('widget2', existingLayout);
|
||||
expect(result).toEqual({
|
||||
i: 'widget2',
|
||||
x: 6,
|
||||
y: 0,
|
||||
w: 6,
|
||||
h: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('should place widget at bottom when last row is full', () => {
|
||||
const existingLayout = [
|
||||
{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 },
|
||||
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
|
||||
];
|
||||
const result = placeWidgetAtBottom('widget3', existingLayout);
|
||||
expect(result).toEqual({
|
||||
i: 'widget3',
|
||||
x: 0,
|
||||
y: 6,
|
||||
w: 6,
|
||||
h: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle multiple rows correctly', () => {
|
||||
const existingLayout = [
|
||||
{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 },
|
||||
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
|
||||
{ i: 'widget3', x: 0, y: 6, w: 6, h: 6 },
|
||||
];
|
||||
const result = placeWidgetAtBottom('widget4', existingLayout);
|
||||
expect(result).toEqual({
|
||||
i: 'widget4',
|
||||
x: 6,
|
||||
y: 6,
|
||||
w: 6,
|
||||
h: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle widgets with different heights', () => {
|
||||
const existingLayout = [
|
||||
{ i: 'widget1', x: 0, y: 0, w: 6, h: 8 },
|
||||
{ i: 'widget2', x: 6, y: 0, w: 6, h: 4 },
|
||||
];
|
||||
const result = placeWidgetAtBottom('widget3', existingLayout);
|
||||
// y = 2 here as later the react-grid-layout will add 2px to the y value while adjusting the layout
|
||||
expect(result).toEqual({
|
||||
i: 'widget3',
|
||||
x: 6,
|
||||
y: 2,
|
||||
w: 6,
|
||||
h: 6,
|
||||
});
|
||||
});
|
||||
});
|
@ -58,6 +58,7 @@ import {
|
||||
getDefaultWidgetData,
|
||||
getIsQueryModified,
|
||||
handleQueryChange,
|
||||
placeWidgetAtBottom,
|
||||
} from './utils';
|
||||
|
||||
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
@ -363,20 +364,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
return;
|
||||
}
|
||||
|
||||
const widgetId = query.get('widgetId');
|
||||
const widgetId = query.get('widgetId') || '';
|
||||
let updatedLayout = selectedDashboard.data.layout || [];
|
||||
|
||||
if (isNewDashboard) {
|
||||
updatedLayout = [
|
||||
{
|
||||
i: widgetId || '',
|
||||
w: 6,
|
||||
x: 0,
|
||||
h: 6,
|
||||
y: 0,
|
||||
},
|
||||
...updatedLayout,
|
||||
];
|
||||
const newLayoutItem = placeWidgetAtBottom(widgetId, updatedLayout);
|
||||
updatedLayout = [...updatedLayout, newLayoutItem];
|
||||
}
|
||||
|
||||
const dashboard: Dashboard = {
|
||||
...selectedDashboard,
|
||||
uuid: selectedDashboard.uuid,
|
||||
|
@ -10,7 +10,8 @@ import {
|
||||
PANEL_TYPES_INITIAL_QUERY,
|
||||
} from 'container/NewDashboard/ComponentsSlider/constants';
|
||||
import { categoryToSupport } from 'container/QueryBuilder/filters/BuilderUnitsFilter/config';
|
||||
import { cloneDeep, isEmpty, isEqual, set, unset } from 'lodash-es';
|
||||
import { cloneDeep, defaultTo, isEmpty, isEqual, set, unset } from 'lodash-es';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@ -575,3 +576,58 @@ export const unitOptions = (columnUnit: string): DefaultOptionType[] => {
|
||||
options: getCategorySelectOptionByName(filteredCategory),
|
||||
}));
|
||||
};
|
||||
|
||||
export const placeWidgetAtBottom = (
|
||||
widgetId: string,
|
||||
layout: Layout[],
|
||||
widgetWidth?: number,
|
||||
widgetHeight?: number,
|
||||
): Layout => {
|
||||
if (layout.length === 0) {
|
||||
return { i: widgetId, x: 0, y: 0, w: widgetWidth || 6, h: widgetHeight || 6 };
|
||||
}
|
||||
|
||||
// Find the maximum Y coordinate and height
|
||||
const { maxY } = layout.reduce(
|
||||
(acc, curr) => ({
|
||||
maxY: Math.max(acc.maxY, curr.y + curr.h),
|
||||
}),
|
||||
{ maxY: 0 },
|
||||
);
|
||||
|
||||
// Check for available space in the last row
|
||||
const lastRowWidgets = layout.filter((item) => item.y + item.h === maxY);
|
||||
const occupiedXInLastRow = lastRowWidgets.reduce(
|
||||
(acc, widget) => acc + widget.w,
|
||||
0,
|
||||
);
|
||||
|
||||
// If there's space in the last row (total width < 12)
|
||||
if (occupiedXInLastRow < 12) {
|
||||
// Find the rightmost X coordinate in the last row
|
||||
const maxXInLastRow = lastRowWidgets.reduce(
|
||||
(acc, widget) => Math.max(acc, widget.x + widget.w),
|
||||
0,
|
||||
);
|
||||
|
||||
// If there's enough space for a 6-width widget
|
||||
if (maxXInLastRow + defaultTo(widgetWidth, 6) <= 12) {
|
||||
return {
|
||||
i: widgetId,
|
||||
x: maxXInLastRow,
|
||||
y: maxY - (widgetHeight || 6), // Align with the last row
|
||||
w: widgetWidth || 6,
|
||||
h: widgetHeight || 6,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If no space in last row, place at the bottom
|
||||
return {
|
||||
i: widgetId,
|
||||
x: 0,
|
||||
y: maxY,
|
||||
w: widgetWidth || 6,
|
||||
h: widgetHeight || 6,
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { convertKeysToColumnFields } from 'container/LogsExplorerList/utils';
|
||||
import { placeWidgetAtBottom } from 'container/NewWidget/utils';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
@ -22,20 +23,16 @@ export const addEmptyWidgetInDashboardJSONWithQuery = (
|
||||
...convertKeysToColumnFields(selectedColumns || []),
|
||||
];
|
||||
|
||||
const newLayoutItem = placeWidgetAtBottom(
|
||||
widgetId,
|
||||
dashboard?.data?.layout || [],
|
||||
);
|
||||
|
||||
return {
|
||||
...dashboard,
|
||||
data: {
|
||||
...dashboard.data,
|
||||
layout: [
|
||||
{
|
||||
i: widgetId,
|
||||
w: 6,
|
||||
x: 0,
|
||||
h: 6,
|
||||
y: 0,
|
||||
},
|
||||
...(dashboard?.data?.layout || []),
|
||||
],
|
||||
layout: [...(dashboard?.data?.layout || []), newLayoutItem],
|
||||
widgets: [
|
||||
...(dashboard?.data?.widgets || []),
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user