mirror of
https://github.com/Graylog2/graylog2-server.git
synced 2026-03-13 09:32:21 +08:00
Add Inputs state dot badge to System and Inputs menu (#23989)
* add Inputs state dot badge to System and Inputs menu * add changelog * extract menu dot badge to own component * adjust title * remove "or false" * add license header
This commit is contained in:
5
changelog/unreleased/pr-23989.toml
Normal file
5
changelog/unreleased/pr-23989.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
type = "a"
|
||||
message = "Add Inputs state dot badge to System and Inputs menu."
|
||||
|
||||
issues = [""]
|
||||
pulls = ["23989"]
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { render, screen } from 'wrappedTestingLibrary';
|
||||
|
||||
import { asMock } from 'helpers/mocking';
|
||||
import type { InputStateSummary } from 'hooks/useInputsStates';
|
||||
import useInputsStates from 'hooks/useInputsStates';
|
||||
|
||||
import InputsDotBadge from './InputsDotBadge';
|
||||
|
||||
jest.mock('hooks/useInputsStates');
|
||||
|
||||
const TEXT = 'Inputs';
|
||||
|
||||
describe('<InputsDotBadge />', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns null while loading', () => {
|
||||
asMock(useInputsStates).mockReturnValue({
|
||||
refetch: jest.fn(),
|
||||
isLoading: true,
|
||||
data: undefined,
|
||||
});
|
||||
|
||||
render(<InputsDotBadge text={TEXT} />);
|
||||
|
||||
expect(screen.queryByText(TEXT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders plain text when there are no failed/failing/setup inputs', () => {
|
||||
asMock(useInputsStates).mockReturnValue({
|
||||
refetch: jest.fn(),
|
||||
isLoading: false,
|
||||
data: {
|
||||
states: [
|
||||
{ id: '1', state: 'RUNNING' } as InputStateSummary,
|
||||
{ id: '2', state: 'STARTING' } as InputStateSummary,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<InputsDotBadge text={TEXT} />);
|
||||
|
||||
const textEl = screen.getByText(TEXT);
|
||||
expect(textEl).toBeInTheDocument();
|
||||
expect(textEl).not.toHaveAttribute('title', 'Some inputs are in failed state or in setup mode.');
|
||||
});
|
||||
|
||||
describe.each(['FAILED', 'FAILING', 'SETUP'])('renders badge when an input state is %s', (problemState) => {
|
||||
it(`shows badge (dot) with tooltip for state ${problemState}`, () => {
|
||||
asMock(useInputsStates).mockReturnValue({
|
||||
refetch: jest.fn(),
|
||||
isLoading: false,
|
||||
data: {
|
||||
states: [
|
||||
{ id: '1', state: 'RUNNING' } as InputStateSummary,
|
||||
{ id: '2', state: problemState } as InputStateSummary,
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
render(<InputsDotBadge text={TEXT} />);
|
||||
|
||||
const badge = screen.getByTitle(/Some inputs are in failed state or in setup mode\./i);
|
||||
expect(badge).toBeInTheDocument();
|
||||
expect(badge).toHaveTextContent(TEXT);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
|
||||
import useInputsStates from 'hooks/useInputsStates';
|
||||
import MenuItemDotBadge from 'components/navigation/MenuItemDotBadge';
|
||||
|
||||
const InputsDotBadge = ({ text }: { text: string }) => {
|
||||
const { data, isLoading } = useInputsStates();
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasFailedOrSetupInputs = data?.states.some((inputState) =>
|
||||
['FAILED', 'FAILING', 'SETUP'].includes(inputState.state),
|
||||
);
|
||||
|
||||
return (
|
||||
<MenuItemDotBadge
|
||||
text={text}
|
||||
title="Some inputs are in failed state or in setup mode."
|
||||
showDot={hasFailedOrSetupInputs}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default InputsDotBadge;
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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>.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Badge = styled.span`
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
display: 'block';
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
||||
background-color: ${({ theme }) => theme.colors.brand.primary};
|
||||
border-radius: 50%;
|
||||
top: 0;
|
||||
right: -12px;
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
text: string;
|
||||
title: string;
|
||||
showDot: boolean;
|
||||
};
|
||||
|
||||
const MenuItemDotBadge = ({ text, title, showDot }: Props) => {
|
||||
if (!showDot) {
|
||||
return <span>{text}</span>;
|
||||
}
|
||||
|
||||
return <Badge title={title}>{text}</Badge>;
|
||||
};
|
||||
|
||||
export default MenuItemDotBadge;
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import type { PluginExports } from 'graylog-web-plugin/plugin';
|
||||
|
||||
import InputsDotBadge from 'components/inputs/InputsDotBadge';
|
||||
import Routes from 'routing/Routes';
|
||||
import filterMenuItems, { filterCloudMenuItems } from 'util/conditional/filterMenuItems';
|
||||
import AppConfig from 'util/AppConfig';
|
||||
@@ -45,6 +46,7 @@ const navigationBindings: PluginExports = {
|
||||
},
|
||||
{
|
||||
description: SYSTEM_DROPDOWN_TITLE,
|
||||
BadgeComponent: InputsDotBadge,
|
||||
position: { last: true },
|
||||
children: filterCloudMenuItems(
|
||||
filterMenuItems(
|
||||
@@ -60,7 +62,12 @@ const navigationBindings: PluginExports = {
|
||||
description: 'Cluster Configuration',
|
||||
permissions: ['clusterconfiguration:read'],
|
||||
},
|
||||
{ path: Routes.SYSTEM.INPUTS, description: 'Inputs', permissions: ['inputs:read'] },
|
||||
{
|
||||
path: Routes.SYSTEM.INPUTS,
|
||||
description: 'Inputs',
|
||||
permissions: ['inputs:read'],
|
||||
BadgeComponent: InputsDotBadge,
|
||||
},
|
||||
{ path: Routes.SYSTEM.OUTPUTS, description: 'Outputs', permissions: ['outputs:read'] },
|
||||
{ path: Routes.SYSTEM.INDICES.LIST, description: 'Indices', permissions: ['indices:read'] },
|
||||
{ path: Routes.SYSTEM.LOGGING, description: 'Logging', permissions: ['loggers:read'] },
|
||||
|
||||
85
graylog2-web-interface/src/hooks/useInputsStates.ts
Normal file
85
graylog2-web-interface/src/hooks/useInputsStates.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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>.
|
||||
*/
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { SystemInputStates } from '@graylog/server-api';
|
||||
|
||||
import { defaultOnError } from 'util/conditional/onError';
|
||||
|
||||
export const INPUTS_STATES_QUERY_KEY = ['inputs_states'];
|
||||
|
||||
export type InputState = 'RUNNING' | 'FAILED' | 'STOPPED' | 'STARTING' | 'FAILING' | 'SETUP';
|
||||
|
||||
export type InputSummary = {
|
||||
creator_user_id: string;
|
||||
node: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
global: boolean;
|
||||
attributes: {
|
||||
[key: string]: object;
|
||||
};
|
||||
id: string;
|
||||
title: string;
|
||||
type: string;
|
||||
content_pack: string;
|
||||
static_fields: {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
export type InputStateSummary = {
|
||||
detailed_message: string;
|
||||
started_at: string;
|
||||
id: string;
|
||||
state: string;
|
||||
message_input: InputSummary;
|
||||
};
|
||||
export type InputStatesList = {
|
||||
states: Array<InputStateSummary>;
|
||||
};
|
||||
|
||||
type Options = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
const useInputsStates = (
|
||||
{ enabled }: Options = { enabled: true },
|
||||
): {
|
||||
data: InputStatesList | undefined;
|
||||
refetch: () => void;
|
||||
isLoading: boolean;
|
||||
} => {
|
||||
const { data, refetch, isLoading } = useQuery({
|
||||
queryKey: INPUTS_STATES_QUERY_KEY,
|
||||
|
||||
queryFn: () =>
|
||||
defaultOnError(
|
||||
SystemInputStates.list(),
|
||||
'Loading inputs states failed with status',
|
||||
'Could not load inputs states',
|
||||
),
|
||||
enabled,
|
||||
});
|
||||
|
||||
return {
|
||||
data: data || { states: [] },
|
||||
refetch,
|
||||
isLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export default useInputsStates;
|
||||
Reference in New Issue
Block a user