From f75050d4245a575d4f8c57acdf6a6d97db36a9f2 Mon Sep 17 00:00:00 2001 From: Matej Kubinec <32638572+matejkubinec@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:57:06 +0100 Subject: [PATCH 1/5] PMM-13548 Add service node selection fixes (#787) * PMM-13548 Use FinalForm to store selected node & preselect pmm-server * PMM-13548 Fix warnings in unit tests related to nodes fetching --- .../FormParts/FormParts.test.tsx | 8 ++++--- .../MongoDBConnectionDetails.test.tsx | 8 ++++--- .../MySQLConnectionDetails.test.tsx | 8 ++++--- .../NodesAgents/NodesAgents.constants.tsx | 1 + .../NodesAgents/NodesAgents.test.tsx | 10 ++++++++ .../FormParts/NodesAgents/NodesAgents.tsx | 24 +++++++++++++++---- .../PostgreSQLConnectionDetails.test.tsx | 12 ++++++---- 7 files changed, 52 insertions(+), 19 deletions(-) diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.test.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.test.tsx index f7050cf756c07..129effdf898a1 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.test.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import { FormApi, FormState } from 'final-form'; import React from 'react'; import { Form } from 'react-final-form'; @@ -13,6 +13,8 @@ import { trackingOptions, rdsTrackingOptions } from './FormParts.constants'; import { LabelsFormPart } from './Labels/Labels'; import { MainDetailsFormPart } from './MainDetails/MainDetails'; +jest.mock('app/percona/inventory/Inventory.service'); + const form: Partial = { change: jest.fn(), /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ @@ -31,7 +33,7 @@ describe('MainDetailsFormPart ::', () => { ); const fields = container.querySelectorAll('input'); - expect(fields.length).toBe(8); + await waitFor(() => expect(fields.length).toBe(8)); expect(screen.getByTestId('address-text-input')).toBeDisabled(); expect(screen.getByTestId('serviceName-text-input')).not.toBeDisabled(); @@ -51,7 +53,7 @@ describe('MainDetailsFormPart ::', () => { ); const fields = container.querySelectorAll('input'); - expect(fields.length).toBe(8); + await waitFor(() => expect(fields.length).toBe(8)); expect(screen.getByTestId('address-text-input')).not.toBeDisabled(); expect(screen.getByTestId('serviceName-text-input')).not.toBeDisabled(); diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MongoDBConnectionDetails/MongoDBConnectionDetails.test.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MongoDBConnectionDetails/MongoDBConnectionDetails.test.tsx index 8057d857b0813..6839e2b38ac5b 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MongoDBConnectionDetails/MongoDBConnectionDetails.test.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MongoDBConnectionDetails/MongoDBConnectionDetails.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { Form } from 'react-final-form'; import { Provider } from 'react-redux'; @@ -7,8 +7,10 @@ import { configureStore } from 'app/store/configureStore'; import { MongoDBConnectionDetails } from './MongoDBConnectionDetails'; +jest.mock('app/percona/inventory/Inventory.service'); + describe('MongoDB connection details:: ', () => { - it('should have max query length attribute', () => { + it('should have max query length attribute', async () => { render(
} /> @@ -18,6 +20,6 @@ describe('MongoDB connection details:: ', () => { const textInput = screen.getByTestId('maxQueryLength-text-input'); fireEvent.change(textInput, { target: { value: '1000' } }); - expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000'); + await waitFor(() => expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000')); }); }); diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MySQLConnectionDetails/MySQLConnectionDetails.test.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MySQLConnectionDetails/MySQLConnectionDetails.test.tsx index 902f9960fe5e6..23d57c20ef92e 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MySQLConnectionDetails/MySQLConnectionDetails.test.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/MySQLConnectionDetails/MySQLConnectionDetails.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { Form } from 'react-final-form'; import { Provider } from 'react-redux'; @@ -7,8 +7,10 @@ import { configureStore } from 'app/store/configureStore'; import { MySQLConnectionDetails } from './MySQLConnectionDetails'; +jest.mock('app/percona/inventory/Inventory.service'); + describe('MySQL connection details:: ', () => { - it('should have max query length attribute', () => { + it('should have max query length attribute', async () => { render( } /> @@ -18,6 +20,6 @@ describe('MySQL connection details:: ', () => { const textInput = screen.getByTestId('maxQueryLength-text-input'); fireEvent.change(textInput, { target: { value: '1000' } }); - expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000'); + await waitFor(() => expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000')); }); }); diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants.tsx index 262ceefc161b7..bc62f0170f771 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants.tsx @@ -1 +1,2 @@ +export const PMM_SERVER_NODE_ID = 'pmm-server'; export const PMM_SERVER_NODE_AGENT_ID = 'pmm-server'; diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.test.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.test.tsx index 6e0ace17322a8..05345b66a8084 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.test.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.test.tsx @@ -46,6 +46,16 @@ describe('Nodes Agents:: ', () => { submitMock.mockClear(); }); + it('should pick pmm-server node by default when available', async () => { + jest.spyOn(InventoryService, 'getNodes').mockReturnValue(Promise.resolve({ nodes: nodesMock })); + + setup(); + + await waitFor(() => expect(fetchNodesActionActionSpy).toHaveBeenCalled()); + + await waitFor(() => expect(screen.getByTestId('node')).toHaveTextContent(nodesMock[0].node_id)); + }); + it('should not pick any agent when the selected node is not pmm-server', async () => { jest .spyOn(InventoryService, 'getNodes') diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.tsx index e63447f677744..02bec8011d9c9 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.tsx @@ -1,9 +1,13 @@ -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FC, useCallback, useEffect, useMemo } from 'react'; +import { useField } from 'react-final-form'; import { useStyles2 } from '@grafana/ui'; import { Messages } from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.messages'; import { getStyles } from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/FormParts.styles'; -import { PMM_SERVER_NODE_AGENT_ID } from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants'; +import { + PMM_SERVER_NODE_AGENT_ID, + PMM_SERVER_NODE_ID, +} from 'app/percona/add-instance/components/AddRemoteInstance/FormParts/NodesAgents/NodesAgents.constants'; 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'; @@ -14,6 +18,7 @@ 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 { validators } from 'app/percona/shared/helpers/validatorsForm'; import { useAppDispatch } from 'app/store/store'; import { useSelector } from 'app/types'; @@ -21,8 +26,10 @@ export const NodesAgents: FC = ({ form }) => { const styles = useStyles2(getStyles); const dispatch = useAppDispatch(); const [generateToken] = useCancelToken(); - const [selectedNode, setSelectedNode] = useState(); const { nodes } = useSelector(getNodes); + const { + input: { value: selectedNode }, + } = useField('node'); const nodesOptions = useMemo(() => nodesOptionsMapper(nodes), [nodes]); @@ -49,7 +56,7 @@ export const NodesAgents: FC = ({ form }) => { }; const setNodeAndAgent = (value: NodesOption) => { - setSelectedNode(value); + form?.change('node', value); let selectedAgent: AgentsOption | undefined; if (value.agents && value.agents?.length > 1) { @@ -68,6 +75,12 @@ export const NodesAgents: FC = ({ form }) => { useEffect(() => { if (nodesOptions.length === 0) { loadData(); + } else if (!selectedNode) { + // preselect pmm-server node + const pmmServerNode = nodesOptions.find((node) => node.value === PMM_SERVER_NODE_ID); + if (pmmServerNode) { + setNodeAndAgent(pmmServerNode); + } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [nodesOptions]); @@ -84,8 +97,8 @@ export const NodesAgents: FC = ({ form }) => { data-testid="nodes-selectbox" onChange={(event) => setNodeAndAgent(event as NodesOption)} className={styles.selectField} - value={selectedNode} aria-label={Messages.form.labels.nodesAgents.nodes} + validators={[validators.required]} />
@@ -99,6 +112,7 @@ export const NodesAgents: FC = ({ form }) => { onChange={(event) => changeAgentValue(event as AgentsOption)} className={styles.selectField} aria-label={Messages.form.labels.nodesAgents.agents} + validators={selectedNode ? [validators.required] : undefined} />
diff --git a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/PostgreSQLConnectionDetails/PostgreSQLConnectionDetails.test.tsx b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/PostgreSQLConnectionDetails/PostgreSQLConnectionDetails.test.tsx index 4b817753588a9..1b0115c062af3 100644 --- a/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/PostgreSQLConnectionDetails/PostgreSQLConnectionDetails.test.tsx +++ b/public/app/percona/add-instance/components/AddRemoteInstance/FormParts/PostgreSQLConnectionDetails/PostgreSQLConnectionDetails.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { Form } from 'react-final-form'; import { Provider } from 'react-redux'; @@ -7,8 +7,10 @@ import { configureStore } from 'app/store/configureStore'; import { PostgreSQLConnectionDetails } from './PostgreSQLConnectionDetails'; +jest.mock('app/percona/inventory/Inventory.service'); + describe('PostgreSQL connection details:: ', () => { - it('should have database attribute', () => { + it('should have database attribute', async () => { render( } /> @@ -18,10 +20,10 @@ describe('PostgreSQL connection details:: ', () => { const textInput = screen.getByTestId('database-text-input'); fireEvent.change(textInput, { target: { value: 'db1' } }); - expect(screen.getByTestId('database-text-input')).toHaveValue('db1'); + await waitFor(() => expect(screen.getByTestId('database-text-input')).toHaveValue('db1')); }); - it('should have max query length attribute', () => { + it('should have max query length attribute', async () => { render( } /> @@ -31,6 +33,6 @@ describe('PostgreSQL connection details:: ', () => { const textInput = screen.getByTestId('maxQueryLength-text-input'); fireEvent.change(textInput, { target: { value: '1000' } }); - expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000'); + await waitFor(() => expect(screen.getByTestId('maxQueryLength-text-input')).toHaveValue('1000')); }); }); From 397ddcd052e1f73057eea13405b8bb079603dee7 Mon Sep 17 00:00:00 2001 From: Matej Kubinec <32638572+matejkubinec@users.noreply.github.com> Date: Wed, 18 Dec 2024 15:11:56 +0100 Subject: [PATCH 2/5] PMM-13542 Reload templates by default (#786) --- .../components/AlertRuleTemplate/AlertRuleTemplate.service.ts | 2 +- .../components/AlertRuleTemplate/AlertRuleTemplate.types.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.service.ts b/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.service.ts index 1e2b6ce870ade..4880e6c76f0b8 100644 --- a/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.service.ts +++ b/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.service.ts @@ -21,7 +21,7 @@ export const AlertRuleTemplateService = { return api .get(BASE_URL, false, { cancelToken: token, - params: { ...payload }, + params: { ...payload, reload: true }, }) .then( ({ totals, templates = [] }): TemplatesList => ({ diff --git a/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.types.ts b/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.types.ts index a8cd25a480947..0295e1c3e58a7 100644 --- a/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.types.ts +++ b/public/app/percona/integrated-alerting/components/AlertRuleTemplate/AlertRuleTemplate.types.ts @@ -18,6 +18,7 @@ export interface AlertRuleTemplateGetPayload { page_size: number; index: number; }; + reload?: boolean; } interface AlertRuleTemplatesTotals { From af95a74978cf5a1bbb879d6b3fd00ce49723063c Mon Sep 17 00:00:00 2001 From: Dora <103416234+doracretu3pillar@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:51:04 +0200 Subject: [PATCH 3/5] Replaced updateDescription and added sorting to the versions (#789) --- .../PerconaUpdateVersion.constants.ts | 6 +++--- .../PerconaUpdateVersion.styles.ts | 3 +++ .../PerconaUpdateVersion.tsx | 8 ++++---- .../core/reducers/updates/updates.utils.ts | 16 +++++++++------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.constants.ts b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.constants.ts index 2f35d18455960..7929df9306b39 100644 --- a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.constants.ts +++ b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.constants.ts @@ -1,12 +1,12 @@ export const Messages = { titleOneUpdate: 'New update available', titleMultipleUpdates: 'New updates available', - howToUpdate: 'How to update', - howToUpdateDescription: - "We are inaugurating a new process for updating PMM. It's a new interface with an improved user experience and is ready for the future of PMM. Click on Go to Updates Page to find out more.", newVersions: 'New Versions', readMore: 'Read more', fullReleaseNotes: 'Full release notes here', goToUpdatesPage: 'Go to updates page', snooze: 'Dismiss', + readyForAnUpdate: 'Ready for an upgrade?', + updateDescription: + "We've streamlined our update process with a new, user-friendly interface. One click gets you all the latest improvements.", }; diff --git a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.styles.ts b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.styles.ts index 45dd1980d2bf0..eaa4d4d3fee77 100644 --- a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.styles.ts +++ b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.styles.ts @@ -40,4 +40,7 @@ export const getStyles = ({ v1: { spacing, colors, typography } }: GrafanaTheme2 color: ${colors.textBlue}; }, `, + readyForUpdate: css` + margin-top: 10px; + `, }); diff --git a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx index e5ad28d961162..45811294dde4f 100644 --- a/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx +++ b/public/app/percona/shared/components/PerconaBootstrapper/PerconaUpdateVersion/PerconaUpdateVersion.tsx @@ -67,8 +67,8 @@ const PerconaUpdateVersion = () => { {Messages.fullReleaseNotes}

-
{Messages.howToUpdate}
-

{Messages.howToUpdateDescription}

+
{Messages.readyForAnUpdate}
+ {Messages.updateDescription}