Skip to content

Commit

Permalink
Fix new Datanode EntityDataTable issues (#20495)
Browse files Browse the repository at this point in the history
* add DataNodeBulkActions back

* changelog file

* unselect after bulk action success

* add back refetch interval

* refetch datanodes after row action

* refetch datanodes after bulk action

* add clear timeout to sleep function
  • Loading branch information
gally47 authored Sep 20, 2024
1 parent df20f3d commit 251fb88
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 8 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/issue-20481.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type="f"
message="Fix new Datanode EntityDataTable issues"

issues=["20481"]
pulls=["20495"]
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { useState } from 'react';
import { useState, useRef } from 'react';
import styled from 'styled-components';

import { ConfirmDialog } from 'components/common';
import { Button, MenuItem } from 'components/bootstrap';
import type { DataNode } from 'preflight/types';
import { MoreActions } from 'components/common/EntityDataTable';
import { useTableFetchContext } from 'components/common/PaginatedEntityTable';
import sleep from 'logic/sleep';

import DataNodeLogsDialog from './DataNodeLogsDialog';

Expand Down Expand Up @@ -68,6 +70,26 @@ const DataNodeActions = ({ dataNode, displayAs }: Props) => {
const [showLogsDialog, setShowLogsDialog] = useState(false);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [dialogType, setDialogType] = useState(null);
const { refetch } = useTableFetchContext();
const statusTimeout = useRef<ReturnType<typeof setTimeout>>();

const sleepAndClearTimer = async () => {
if (statusTimeout.current) {
clearTimeout(statusTimeout.current);
}

statusTimeout.current = await sleep(1000);
};

const refetchDatanodes = async () => {
await sleepAndClearTimer();
await refetch();
};

const handleStartDatanode = async () => {
await startDataNode(dataNode.node_id);
await refetchDatanodes();
};

const updateState = ({ show, type }) => {
setShowConfirmDialog(show);
Expand All @@ -93,8 +115,9 @@ const DataNodeActions = ({ dataNode, displayAs }: Props) => {
}
};

const handleClearState = () => {
const handleClearState = async () => {
updateState({ show: false, type: null });
await refetchDatanodes();
};

const handleConfirm = () => {
Expand Down Expand Up @@ -131,7 +154,7 @@ const DataNodeActions = ({ dataNode, displayAs }: Props) => {
{displayAs === 'dropdown' && (
<MoreActions>
<MenuItem onSelect={() => renewDatanodeCertificate(dataNode.node_id)}>Renew certificate</MenuItem>
{!isDatanodeRunning && <MenuItem onSelect={() => startDataNode(dataNode.node_id)}>Start</MenuItem>}
{!isDatanodeRunning && <MenuItem onSelect={handleStartDatanode}>Start</MenuItem>}
{isDatanodeRunning && <MenuItem onSelect={() => handleAction(DIALOG_TYPES.STOP)}>Stop</MenuItem>}
{isDatanodeRemoved && <MenuItem onSelect={() => handleAction(DIALOG_TYPES.REJOIN)}>Rejoin</MenuItem>}
{(!isDatanodeRemoved || isRemovingDatanode) && <MenuItem onSelect={() => handleAction(DIALOG_TYPES.REMOVE)}>Remove</MenuItem>}
Expand All @@ -140,7 +163,7 @@ const DataNodeActions = ({ dataNode, displayAs }: Props) => {
)}
{displayAs === 'buttons' && (
<>
{!isDatanodeRunning && <ActionButton onClick={() => startDataNode(dataNode.node_id)} bsSize="small">Start</ActionButton>}
{!isDatanodeRunning && <ActionButton onClick={handleStartDatanode} bsSize="small">Start</ActionButton>}
{isDatanodeRunning && <ActionButton onClick={() => handleAction(DIALOG_TYPES.STOP)} bsSize="small">Stop</ActionButton>}
{isDatanodeRemoved && <ActionButton onClick={() => handleAction(DIALOG_TYPES.REJOIN)} bsSize="small">Rejoin</ActionButton>}
{(!isDatanodeRemoved || isRemovingDatanode) && <ActionButton onClick={() => handleAction(DIALOG_TYPES.REMOVE)} bsSize="small">Remove</ActionButton>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,66 @@
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { useState } from 'react';
import { useState, useRef } from 'react';

import MenuItem from 'components/bootstrap/MenuItem';
import BulkActionsDropdown from 'components/common/EntityDataTable/BulkActionsDropdown';
import useSelectedEntities from 'components/common/EntityDataTable/hooks/useSelectedEntities';
import ConfirmDialog from 'components/common/ConfirmDialog';
import { useTableFetchContext } from 'components/common/PaginatedEntityTable';
import sleep from 'logic/sleep';

import { bulkRemoveDataNode, bulkStartDataNode, bulkStopDataNode } from '../hooks/useDataNodes';

const DataNodeBulkActions = () => {
const { selectedEntities, setSelectedEntities } = useSelectedEntities();
const [showDialogType, setShowDialogType] = useState<'REMOVE'|'STOP'|null>(null);
const { refetch } = useTableFetchContext();
const statusTimeout = useRef<ReturnType<typeof setTimeout>>();

const sleepAndClearTimer = async () => {
if (statusTimeout.current) {
clearTimeout(statusTimeout.current);
}

statusTimeout.current = await sleep(1000);
};

const refetchDatanodes = async () => {
await sleepAndClearTimer();
await refetch();
};

const handleBulkStartDatanode = async () => {
await bulkStartDataNode(selectedEntities, setSelectedEntities);
await refetchDatanodes();
};

const CONFIRM_DIALOG = {
REMOVE: {
dialogTitle: 'Remove Data Nodes',
dialogBody: `Are you sure you want to remove the selected ${selectedEntities.length > 1 ? `${selectedEntities.length} Data Nodes` : 'Data Node'}?`,
handleConfirm: () => {
handleConfirm: async () => {
bulkRemoveDataNode(selectedEntities, setSelectedEntities);
setShowDialogType(null);
await refetchDatanodes();
},
},
STOP: {
dialogTitle: 'Stop Data Nodes',
dialogBody: `Are you sure you want to stop the selected ${selectedEntities.length > 1 ? `${selectedEntities.length} Data Nodes` : 'Data Node'}?`,
handleConfirm: () => {
handleConfirm: async () => {
bulkStopDataNode(selectedEntities, setSelectedEntities);
setShowDialogType(null);
await refetchDatanodes();
},
},
};

return (
<>
<BulkActionsDropdown>
<MenuItem onSelect={() => bulkStartDataNode(selectedEntities, setSelectedEntities)}>Start</MenuItem>
<MenuItem onSelect={handleBulkStartDatanode}>Start</MenuItem>
<MenuItem onSelect={() => setShowDialogType('STOP')}>Stop</MenuItem>
<MenuItem onSelect={() => setShowDialogType('REMOVE')}>Remove</MenuItem>
</BulkActionsDropdown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import Routes from 'routing/Routes';

import DataNodeActions from './DataNodeActions';
import DataNodeStatusCell from './DataNodeStatusCell';
import DataNodeBulkActions from './DataNodeBulkActions';

import { fetchDataNodes, keyFn } from '../hooks/useDataNodes';

Expand Down Expand Up @@ -76,9 +77,11 @@ const DataNodeList = () => (
</p>
)} />
)}
bulkSelection={{ actions: <DataNodeBulkActions /> }}
entityActions={entityActions}
tableLayout={DEFAULT_LAYOUT}
fetchEntities={fetchDataNodes}
fetchOptions={{ refetchInterval: 5000 }}
keyFn={keyFn}
entityAttributesAreCamelCase={false}
columnRenderers={columnRenderers} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const bulkRemoveDataNode = async (entity_ids: string[], selectBackFailedE
try {
const { failures, successfully_performed } = await fetch('POST', qualifyUrl('/datanode/bulk_remove'), { entity_ids });

selectBackFailedEntities([]);

if (failures?.length) {
selectBackFailedEntities(failures.map(({ entity_id }) => entity_id));
}
Expand All @@ -48,6 +50,8 @@ export const bulkStartDataNode = async (entity_ids: string[], selectBackFailedEn
try {
const { failures, successfully_performed } = await fetch('POST', qualifyUrl('/datanode/bulk_start'), { entity_ids });

selectBackFailedEntities([]);

if (failures?.length) {
selectBackFailedEntities(failures.map(({ entity_id }) => entity_id));
}
Expand All @@ -68,6 +72,8 @@ export const bulkStopDataNode = async (entity_ids: string[], selectBackFailedEnt
try {
const { failures, successfully_performed } = await fetch('POST', qualifyUrl('/datanode/bulk_stop'), { entity_ids });

selectBackFailedEntities([]);

if (failures?.length) {
selectBackFailedEntities(failures.map(({ entity_id }) => entity_id));
}
Expand Down
21 changes: 21 additions & 0 deletions graylog2-web-interface/src/logic/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (C) 2020 Graylog, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
const sleep = async (milliseconds: number): Promise<ReturnType<typeof setTimeout>> => new Promise((resolve) => {
const timerID = setTimeout(() => resolve(timerID), milliseconds);
});

export default sleep;

0 comments on commit 251fb88

Please sign in to comment.