Skip to content

Commit

Permalink
feat: added feat to add new panel in a section (#6999)
Browse files Browse the repository at this point in the history
* feat: added common util and took possible space available in last row in account

* feat: added different test cases

* feat: remove console.log

* feat: added default value to widgetWidth

* feat: added feat to add new panel in a section

* feat: added different test cases
  • Loading branch information
SagarRajput-7 authored Feb 11, 2025
1 parent d22ecb9 commit 42fad23
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function DashboardEmptyState(): JSX.Element {
selectedDashboard,
isDashboardLocked,
handleToggleDashboardSlider,
setSelectedRowWidgetId,
} = useDashboard();

const { user } = useAppContext();
Expand All @@ -34,6 +35,7 @@ export default function DashboardEmptyState(): JSX.Element {
const [addPanelPermission] = useComponentPermission(permissions, userRole);

const onEmptyWidgetHandler = useCallback(() => {
setSelectedRowWidgetId(null);
handleToggleDashboardSlider(true);
logEvent('Dashboard Detail: Add new panel clicked', {
dashboardId: selectedDashboard?.uuid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@

.menu-content {
.section-1 {
.rename-btn {
.rename-btn,
.new-panel-btn {
display: flex;
align-items: center;
gap: 6px;
Expand All @@ -150,6 +151,10 @@
margin-inline-end: 0px;
}
}

.rename-btn {
padding-bottom: 10px;
}
}

.section-2 {
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/container/GridCardLayout/GridCardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
isDashboardLocked,
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
setSelectedRowWidgetId,
} = useDashboard();
const { data } = selectedDashboard || {};
const { pathname } = useLocation();
Expand Down Expand Up @@ -174,6 +175,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {

updateDashboardMutation.mutate(updatedDashboard, {
onSuccess: (updatedDashboard) => {
setSelectedRowWidgetId(null);
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
Expand Down
38 changes: 37 additions & 1 deletion frontend/src/container/GridCardLayout/WidgetRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Button, Popover } from 'antd';
import { EllipsisIcon, PenLine, X } from 'lucide-react';
import useComponentPermission from 'hooks/useComponentPermission';
import { EllipsisIcon, PenLine, Plus, X } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useState } from 'react';
import { Layout } from 'react-grid-layout';
import { ROLES, USER_ROLES } from 'types/roles';
import { ComponentTypes } from 'utils/permission';

interface WidgetRowHeaderProps {
rowWidgetProperties: {
Expand All @@ -27,6 +32,23 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
id,
} = props;
const [isRowSettingsOpen, setIsRowSettingsOpen] = useState<boolean>(false);

const {
handleToggleDashboardSlider,
selectedDashboard,
isDashboardLocked,
setSelectedRowWidgetId,
} = useDashboard();

const permissions: ComponentTypes[] = ['add_panel'];
const { user } = useAppContext();

const userRole: ROLES | null =
selectedDashboard?.created_by === user?.email
? (USER_ROLES.AUTHOR as ROLES)
: user.role;
const [addPanelPermission] = useComponentPermission(permissions, userRole);

return (
<Popover
open={isRowSettingsOpen}
Expand All @@ -52,6 +74,20 @@ export function WidgetRowHeader(props: WidgetRowHeaderProps): JSX.Element {
Rename
</Button>
</section>
<section className="section-1">
<Button
className="new-panel-btn"
type="text"
disabled={!editWidget && addPanelPermission && !isDashboardLocked}
icon={<Plus size={14} />}
onClick={(): void => {
setSelectedRowWidgetId(id);
handleToggleDashboardSlider(true);
}}
>
New Panel
</Button>
</section>
{!rowWidgetProperties.collapsed && (
<section className="section-2">
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
listSortOrder,
setSelectedDashboard,
handleToggleDashboardSlider,
setSelectedRowWidgetId,
handleDashboardLockToggle,
} = useDashboard();

Expand Down Expand Up @@ -157,6 +158,7 @@ function DashboardDescription(props: DashboardDescriptionProps): JSX.Element {
const [addPanelPermission] = useComponentPermission(permissions, userRole);

const onEmptyWidgetHandler = useCallback(() => {
setSelectedRowWidgetId(null);
handleToggleDashboardSlider(true);
logEvent('Dashboard Detail: Add new panel clicked', {
dashboardId: selectedDashboard?.uuid,
Expand Down
128 changes: 127 additions & 1 deletion frontend/src/container/NewWidget/__test__/NewWidget.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// - Handling multiple rows correctly
// - Handling widgets with different heights

import { placeWidgetAtBottom } from '../utils';
import { placeWidgetAtBottom, placeWidgetBetweenRows } from '../utils';

describe('placeWidgetAtBottom', () => {
it('should place widget at (0,0) when layout is empty', () => {
Expand Down Expand Up @@ -90,3 +90,129 @@ describe('placeWidgetAtBottom', () => {
});
});
});

describe('placeWidgetBetweenRows', () => {
it('should return single widget layout when layout is empty', () => {
const result = placeWidgetBetweenRows('widget1', [], 'currentRow');
expect(result).toEqual([
{
i: 'widget1',
x: 0,
y: 0,
w: 6,
h: 6,
},
]);
});

it('should place widget at the end of the layout when no nextRowId is provided', () => {
const existingLayout = [
{ i: 'widget1', x: 0, y: 0, w: 6, h: 6 },
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
];

const result = placeWidgetBetweenRows('widget3', existingLayout, 'widget2');

expect(result).toEqual([
{ 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 },
]);
});

it('should place widget between current and next row', () => {
const existingLayout = [
{
h: 1,
i: "'widget1'",
maxH: 1,
minH: 1,
minW: 12,
moved: false,
static: false,
w: 12,
x: 0,
y: 0,
},
{ i: 'widget2', x: 6, y: 0, w: 6, h: 6 },
{
h: 1,
i: 'widget3',
maxH: 1,
minH: 1,
minW: 12,
moved: false,
static: false,
w: 12,
x: 0,
y: 7,
},
];

const result = placeWidgetBetweenRows(
'widget4',
existingLayout,
'widget1',
'widget3',
);

expect(result).toEqual([
{
h: 1,
i: "'widget1'",
maxH: 1,
minH: 1,
minW: 12,
moved: false,
static: false,
w: 12,
x: 0,
y: 0,
},
{
h: 6,
i: 'widget2',
w: 6,
x: 6,
y: 0,
},
{
h: 6,
i: 'widget4',
w: 6,
x: 0,
y: 6,
},
{
h: 1,
i: 'widget3',
maxH: 1,
minH: 1,
minW: 12,
moved: false,
static: false,
w: 12,
x: 0,
y: 7,
},
]);
});

it('should respect custom widget dimensions', () => {
const existingLayout = [{ i: 'widget1', x: 0, y: 0, w: 12, h: 4 }];

const result = placeWidgetBetweenRows(
'widget2',
existingLayout,
'widget1',
null,
8,
3,
);

expect(result).toEqual([
{ i: 'widget1', x: 0, y: 0, w: 12, h: 4 },
{ i: 'widget2', x: 0, y: 4, w: 8, h: 3 },
]);
});
});
43 changes: 38 additions & 5 deletions frontend/src/container/NewWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import logEvent from 'api/common/logEvent';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import {
initialQueriesMap,
PANEL_GROUP_TYPES,
PANEL_TYPES,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { DashboardShortcuts } from 'constants/shortcuts/DashboardShortcuts';
import { DEFAULT_BUCKET_COUNT } from 'container/PanelWrapper/constants';
Expand All @@ -20,7 +24,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import history from 'lib/history';
import { defaultTo, isUndefined } from 'lodash-es';
import { defaultTo, isEmpty, isUndefined } from 'lodash-es';
import { Check, X } from 'lucide-react';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
import { useAppContext } from 'providers/App/App';
Expand Down Expand Up @@ -59,13 +63,16 @@ import {
getIsQueryModified,
handleQueryChange,
placeWidgetAtBottom,
placeWidgetBetweenRows,
} from './utils';

function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const {
selectedDashboard,
setSelectedDashboard,
setToScrollWidgetId,
selectedRowWidgetId,
setSelectedRowWidgetId,
} = useDashboard();

const { t } = useTranslation(['dashboard']);
Expand Down Expand Up @@ -367,11 +374,33 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
const widgetId = query.get('widgetId') || '';
let updatedLayout = selectedDashboard.data.layout || [];

if (isNewDashboard) {
if (isNewDashboard && isEmpty(selectedRowWidgetId)) {
const newLayoutItem = placeWidgetAtBottom(widgetId, updatedLayout);
updatedLayout = [...updatedLayout, newLayoutItem];
}

if (isNewDashboard && selectedRowWidgetId) {
// Find the next row by looking through remaining layout items
const currentIndex = updatedLayout.findIndex(
(e) => e.i === selectedRowWidgetId,
);
const nextRowIndex = updatedLayout.findIndex(
(item, index) =>
index > currentIndex &&
widgets?.find((w) => w.id === item.i)?.panelTypes ===
PANEL_GROUP_TYPES.ROW,
);
const nextRowId = nextRowIndex !== -1 ? updatedLayout[nextRowIndex].i : null;

const newLayoutItem = placeWidgetBetweenRows(
widgetId,
updatedLayout,
selectedRowWidgetId,
nextRowId,
);
updatedLayout = newLayoutItem;
}

const dashboard: Dashboard = {
...selectedDashboard,
uuid: selectedDashboard.uuid,
Expand Down Expand Up @@ -437,6 +466,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {

updateDashboardMutation.mutateAsync(dashboard, {
onSuccess: () => {
setSelectedRowWidgetId(null);
setSelectedDashboard(dashboard);
setToScrollWidgetId(selectedWidget?.id || '');
history.push({
Expand All @@ -449,16 +479,19 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
selectedDashboard,
query,
isNewDashboard,
preWidgets,
selectedRowWidgetId,
afterWidgets,
selectedWidget,
selectedTime.enum,
graphType,
currentQuery,
afterWidgets,
preWidgets,
updateDashboardMutation,
handleError,
widgets,
setSelectedDashboard,
setToScrollWidgetId,
setSelectedRowWidgetId,
dashboardId,
]);

Expand Down
Loading

0 comments on commit 42fad23

Please # to comment.