Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PMM-13166: Ability to monitor DBs from a different node #766

Merged
merged 35 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a3ef553
Nodes for MySQLConnection
doracretu3pillar Jul 24, 2024
a0353ac
Added Nodes Agents component
doracretu3pillar Jul 29, 2024
c39224b
Added pmm_agent_id parameter instead of agent/node
doracretu3pillar Jul 29, 2024
98f238a
Added NodesAgents to all the services
doracretu3pillar Aug 5, 2024
218ccdd
Fixes after reviews
doracretu3pillar Aug 16, 2024
2db183c
Testing NodesAgents in progress
doracretu3pillar Aug 21, 2024
c549be1
Added NodesAgents test
doracretu3pillar Aug 22, 2024
af05984
Fix from PR
doracretu3pillar Aug 29, 2024
cd89d7b
Fixes after PR
doracretu3pillar Sep 6, 2024
cc639cd
Prettier fix
DoraCretu594118 Sep 10, 2024
dfb3498
Merge branch 'v3' into PMM-13166_nodes_monitor_db
doracretu3pillar Sep 10, 2024
395a1a7
Fixed ts issues
DoraCretu594118 Sep 10, 2024
f2a327f
Merge branch 'PMM-13166_nodes_monitor_db' of https://github.com/perco…
DoraCretu594118 Sep 10, 2024
088e376
Added the types to mappedNodes, nodesOptions, changed the agent name …
doracretu3pillar Sep 19, 2024
17f0547
Updated tests - partial
doracretu3pillar Sep 19, 2024
ade9992
Merge branch 'v3' into PMM-13166_nodes_monitor_db
doracretu3pillar Sep 20, 2024
fee3eed
Partially tests
doracretu3pillar Sep 20, 2024
44c2d5d
Fixed tests
doracretu3pillar Sep 20, 2024
d64b7e8
Added node_type to node mocks and fixed eslint for NodesAgents
doracretu3pillar Sep 20, 2024
d8318ae
Fixed other tests related to Add Service
doracretu3pillar Sep 20, 2024
b95747b
More tests fixed
doracretu3pillar Sep 20, 2024
78507cb
Fixed prettier
doracretu3pillar Sep 20, 2024
ced9f39
Added spy on console.error
doracretu3pillar Sep 21, 2024
c478147
Added test for console error
doracretu3pillar Sep 21, 2024
40fbcbb
Added console error check to every test
doracretu3pillar Sep 21, 2024
864dfe4
Fixes after PR
doracretu3pillar Sep 23, 2024
68548b7
Replaced the formAPI with taken values from the form, and added new m…
doracretu3pillar Sep 23, 2024
59e041f
SelectedAgent can be undefined
doracretu3pillar Sep 23, 2024
09b6adb
Clear mock before each test
doracretu3pillar Sep 23, 2024
6c07180
Added wait for to last test
doracretu3pillar Sep 23, 2024
24f3a77
If address is localhost then send node_id instead of add_node, and se…
doracretu3pillar Sep 30, 2024
2618cbf
Fixes after PR
doracretu3pillar Sep 30, 2024
09097c3
Fixed prettier
doracretu3pillar Sep 30, 2024
4bd8d20
Merge branch 'v3' into PMM-13166_nodes_monitor_db
doracretu3pillar Sep 30, 2024
4476cbc
Merge branch 'v3' into PMM-13166_nodes_monitor_db
doracretu3pillar Oct 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ export const toPayload = (values: any, discoverName?: string, type?: InstanceAva
}
}

data.pmm_agent_id = values.pmm_agent_id.label;
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved

data.metrics_mode = 1;
delete data.tracking;
delete data.node;

return data;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export const getInstanceData = (instanceType: InstanceAvailableType, credentials
remoteInstanceCredentials: {
metricsParameters: MetricsParameters.manually,
schema: Schema.HTTPS,
pmm_agent_id: "",
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useCallback, useEffect, useMemo } from 'react';

import { useStyles2 } from '@grafana/ui';
import { NodesAgents } from "app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents";
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput';
import { RadioButtonGroupField } from 'app/percona/shared/components/Form/RadioButtonGroup';
import { TextInputField } from 'app/percona/shared/components/Form/TextInput';
Expand Down Expand Up @@ -96,6 +97,7 @@ export const ExternalServiceConnectionDetails: FC<FormPartProps> = ({ form }) =>
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const getStyles = ({ breakpoints, spacing, colors }: GrafanaTheme2) => ({
width: 100%;
margin-right: 5px;
`,
selectFieldWrapper: css`
width: 100%;
`,
selectField: css`
height: 38px;
`,
group: css`
display: flex;
flex-direction: row;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useCallback, useMemo } from 'react';

import { useStyles2 } from '@grafana/ui';
import { NodesAgents } from "app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents";
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput';
import { TextInputField } from 'app/percona/shared/components/Form/TextInput';
import Validators from 'app/percona/shared/helpers/validators';
Expand All @@ -10,7 +11,7 @@ import { Messages } from '../FormParts.messages';
import { getStyles } from '../FormParts.styles';
import { MainDetailsFormPartProps } from '../FormParts.types';

export const HAProxyConnectionDetails: FC<MainDetailsFormPartProps> = ({ remoteInstanceCredentials }) => {
export const HAProxyConnectionDetails: FC<MainDetailsFormPartProps> = ({ form, remoteInstanceCredentials }) => {
const styles = useStyles2(getStyles);

const portValidators = useMemo(() => [validators.required, Validators.validatePort], []);
Expand All @@ -29,6 +30,7 @@ export const HAProxyConnectionDetails: FC<MainDetailsFormPartProps> = ({ remoteI
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useMemo } from 'react';

import { useStyles2 } from '@grafana/ui';
import { NodesAgents } from "app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents";
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput';
import { TextInputField } from 'app/percona/shared/components/Form/TextInput';
import Validators from 'app/percona/shared/helpers/validators';
Expand Down Expand Up @@ -30,6 +31,7 @@ export const MainDetailsFormPart: FC<MainDetailsFormPartProps> = ({ form, remote
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { FC, useMemo } from 'react';

import { useStyles2 } from '@grafana/ui';
import { NodesAgents } from "app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents";
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput';
import { TextInputField } from 'app/percona/shared/components/Form/TextInput';
import Validators from 'app/percona/shared/helpers/validators';
Expand Down Expand Up @@ -31,6 +32,7 @@ export const MongoDBConnectionDetails: FC<MainDetailsFormPartProps> = ({ form, r
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { FC, useMemo } from 'react';

import { useStyles2 } from '@grafana/ui';
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput';
import { PasswordInputField } from 'app/percona/shared/components/Form/PasswordInput'
import { TextInputField } from 'app/percona/shared/components/Form/TextInput';
import Validators from 'app/percona/shared/helpers/validators';
import { validators } from 'app/percona/shared/helpers/validatorsForm';

import { Messages } from '../FormParts.messages';
import { getStyles } from '../FormParts.styles';
import { MainDetailsFormPartProps } from '../FormParts.types';
import { NodesAgents } from '../NodesAgents/NodesAgents';

export const MySQLConnectionDetails: FC<MainDetailsFormPartProps> = ({ form, remoteInstanceCredentials }) => {
const styles = useStyles2(getStyles);
Expand All @@ -32,6 +33,7 @@ export const MySQLConnectionDetails: FC<MainDetailsFormPartProps> = ({ form, rem
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { render, screen, waitFor } from '@testing-library/react';
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
import React from 'react';
import { Provider } from 'react-redux';

import { configureStore } from 'app/store/configureStore';

import { NodesAgents } from './NodesAgents';
import { Form } from 'react-final-form';
import * as NodesReducer from 'app/percona/shared/core/reducers/nodes/nodes.ts'
import selectEvent from "react-select-event";

const fetchNodesActionActionSpy = jest.spyOn(NodesReducer, 'fetchNodesAction');

jest.mock('app/percona/inventory/Inventory.service');

describe('Nodes Agents:: ', () => {

let store = configureStore();
let formAPI;
render(
<Provider store={store}>
<Form
onSubmit={jest.fn()}
render={({ form }) => {
formAPI=form;
return <NodesAgents form={form}/>
}}
/>
</Provider>
);

it('should change the list of agents when changing the nodes', async () => {
matejkubinec marked this conversation as resolved.
Show resolved Hide resolved
await waitFor(() => {
expect(fetchNodesActionActionSpy).toHaveBeenCalled();
});
const nodesSelect = screen.getByLabelText('Nodes');
await selectEvent.select(nodesSelect, ['pmm-server'], { container: document.body });

const agentsSelect = screen.getByLabelText('Agents');
const formValues = formAPI.getState().values;
expect(formValues.pmm_agent_id.value).toBe('pmm-agent');
expect(formValues.node.value).toBe('pmm-server');
});

});
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';

import { useStyles2 } from '@grafana/ui';
import { getStyles } from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.styles';
import { NodesAgentsProps } from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.types';
import { GET_NODES_CANCEL_TOKEN } from 'app/percona/inventory/Inventory.constants';
import { AgentsOption, NodesOption } from 'app/percona/inventory/Inventory.types';
import { SelectField } from 'app/percona/shared/components/Form/SelectFieldCore';
import { useCancelToken } from 'app/percona/shared/components/hooks/cancelToken.hook';
import { nodesOptionsMapper } from 'app/percona/shared/core/reducers/nodes';
import { fetchNodesAction } from 'app/percona/shared/core/reducers/nodes/nodes';
import { getNodes } from 'app/percona/shared/core/selectors';
import { isApiCancelError } from 'app/percona/shared/helpers/api';
import { logger } from 'app/percona/shared/helpers/logger';
import { useAppDispatch } from 'app/store/store';
import { useSelector } from 'app/types';

export const NodesAgents: FC<NodesAgentsProps> = ({ form }) => {
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
const styles = useStyles2(getStyles);
const dispatch = useAppDispatch();
const [generateToken] = useCancelToken();
const [selectedNode, setSelectedNode] = useState<NodesOption>();
const [selectedAgent, setSelectedAgent] = useState<AgentsOption>();
const [nodesOptions, setNodesOptions] = useState<NodesOption[]>([]);
const { nodes } = useSelector(getNodes);

useMemo(() => {
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
if(nodes && nodes.length > 0) {
setNodesOptions(nodesOptionsMapper(nodes));
}
},
[nodes]
);


const loadData = useCallback(async () => {
try {
await dispatch(fetchNodesAction({ token: generateToken(GET_NODES_CANCEL_TOKEN) })).unwrap();
} catch (e) {
if (isApiCancelError(e)) {
return;
}
logger.error(e);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const setNodeAndAgent = (value: NodesOption) => {
setSelectedNode(value);
if (value.agents && value.agents.length > 1) {
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
form?.change('pmm_agent_id', value.agents[1]);
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
} else if (value.agents && value.agents.length === 1) {
form?.change('pmm_agent_id', value.agents[0]);
}

if(selectedAgent && selectedAgent.label !== "pmm-server") {
matejkubinec marked this conversation as resolved.
Show resolved Hide resolved
form?.change('address', 'localhost');
}
}

useEffect(() => {
matejkubinec marked this conversation as resolved.
Show resolved Hide resolved
if(nodesOptions.length === 0) {
loadData();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodesOptions]);

return (
<div className={styles.group}>
<div className={styles.selectFieldWrapper}>
<SelectField
label="Nodes"
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
isSearchable={false}
options={nodesOptions}
name="node"
id="nodes-selectbox"
data-testid="nodes-selectbox"
onChange={ (event) => setNodeAndAgent(event as NodesOption)}
className={styles.selectField}
value={selectedNode}
aria-label="Nodes"
/>
</div>
<div className={styles.selectFieldWrapper}>
<SelectField
label="Agents"
isSearchable={false}
disabled={!selectedNode || !selectedNode.agents || selectedNode.agents.length === 1}
options={selectedNode?.agents || []}
name="pmm_agent_id"
data-testid="agents-selectbox"
onChange={ (event) => setSelectedAgent(event as AgentsOption)}
className={styles.selectField}
aria-label="Agents"
/>
</div>
</div>
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { FormApi } from 'final-form';

export interface NodesAgentsProps {
form?: FormApi;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { validators } from 'app/percona/shared/helpers/validatorsForm';
import { Messages } from '../FormParts.messages';
import { getStyles } from '../FormParts.styles';
import { MainDetailsFormPartProps } from '../FormParts.types';
import { NodesAgents } from "../NodesAgents/NodesAgents";

export const PostgreSQLConnectionDetails: FC<MainDetailsFormPartProps> = ({ form, remoteInstanceCredentials }) => {
const styles = useStyles2(getStyles);
Expand All @@ -31,6 +32,7 @@ export const PostgreSQLConnectionDetails: FC<MainDetailsFormPartProps> = ({ form
/>
<div />
</div>
<NodesAgents form={form} />
<div className={styles.group}>
<TextInputField
name="address"
Expand Down
1 change: 1 addition & 0 deletions public/app/percona/add-instance/panel.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface RemoteInstanceCredentials {
skip_connection_check?: boolean;
tls?: boolean;
tls_skip_verify?: boolean;
pmm_agent_id?: string;
}

export enum InstanceTypesExtra {
Expand Down
13 changes: 12 additions & 1 deletion public/app/percona/inventory/Inventory.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export enum AgentType {
mysql = 'mysql',
mysqldExporter = 'mysqldExporter',
nodeExporter = 'nodeExporter',
pmmAgent = 'pmm_agent',
pmmAgent = 'pmm-agent',
postgresExporter = 'postgresExporter',
postgresql = 'postgresql',
proxysql = 'proxysql',
Expand Down Expand Up @@ -176,3 +176,14 @@ export type FlattenService = DbService &
export type FlattenNode = DbNode & {
type: NodeType;
};

export interface NodesOption {
value: string;
label: string;
agents?: AgentsOption[];
}

export interface AgentsOption {
value: string;
label: string;
}
17 changes: 15 additions & 2 deletions public/app/percona/inventory/Tabs/Agents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AppEvents } from '@grafana/data';
import { Badge, Button, HorizontalGroup, Icon, Link, Modal, TagList, useStyles2 } from '@grafana/ui';
import { Page } from 'app/core/components/Page/Page';
import { GrafanaRouteComponentProps } from 'app/core/navigation/types';
import { Agent, FlattenAgent, ServiceAgentStatus } from 'app/percona/inventory/Inventory.types';
import { Agent, FlattenAgent, Node, ServiceAgentStatus } from 'app/percona/inventory/Inventory.types';
import { SelectedTableRows } from 'app/percona/shared/components/Elements/AnotherTableInstance/Table.types';
import { CheckboxField } from 'app/percona/shared/components/Elements/Checkbox';
import { DetailsRow } from 'app/percona/shared/components/Elements/DetailsRow/DetailsRow';
Expand All @@ -16,6 +16,7 @@ import { ExtendedColumn, FilterFieldTypes, Table } from 'app/percona/shared/comp
import { FormElement } from 'app/percona/shared/components/Form';
import { useCancelToken } from 'app/percona/shared/components/hooks/cancelToken.hook';
import { usePerconaNavModel } from 'app/percona/shared/components/hooks/perconaNavModel';
import { nodeFromDbMapper } from 'app/percona/shared/core/reducers/nodes';
import { fetchNodesAction } from 'app/percona/shared/core/reducers/nodes/nodes';
import { fetchServicesAction } from 'app/percona/shared/core/reducers/services';
import { getNodes, getServices } from 'app/percona/shared/core/selectors';
Expand Down Expand Up @@ -51,10 +52,22 @@ export const Agents: FC<GrafanaRouteComponentProps<{ serviceId: string; nodeId:
const { isLoading: nodesLoading, nodes } = useSelector(getNodes);
const styles = useStyles2(getStyles);

const mappedNodes: Node[] = useMemo(
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
(): Node[] => {
if(nodes.length > 0) {
doracretu3pillar marked this conversation as resolved.
Show resolved Hide resolved
return nodeFromDbMapper(nodes).sort((a, b) => a.nodeName.localeCompare(b.nodeName));
}
return [];
},
[nodes]
);

const service = services.find((s) => s.params.serviceId === match.params.serviceId);
const node = nodes.find((s) => s.nodeId === nodeId);
const node = mappedNodes.find((s) => s.nodeId === nodeId);
const flattenAgents = useMemo(() => data.map((value) => ({ type: value.type, ...value.params })), [data]);



const columns = useMemo(
(): Array<ExtendedColumn<FlattenAgent>> => [
{
Expand Down
Loading
Loading