From 4aa375e3e16c699a380ed38dde6aa303b38a2db6 Mon Sep 17 00:00:00 2001 From: Alexander Tymchuk Date: Mon, 1 Jun 2020 12:01:46 +0300 Subject: [PATCH] SAAS-153 check panel viewer access message (#747) * SASS-153 ditch react-test-renderer * SAAS-153 more tests to check for error messages * SAAS-153 message & tests for the home panel * SAAS-153 add message & tests to the details page * SAAS-153 throw away numeral * SAAS-153 refix the panel scroll again * SAAS-153 ts-compatible data-qa passed to components * SAAS-153 trigger the build --- codecov.yml | 3 + pmm-app/package-lock.json | 5 - pmm-app/package.json | 8 +- .../AddRemoteInstance.test.tsx | 4 +- pmm-app/src/pmm-check-home/CheckPanel.tsx | 10 +- .../components/Failed/Failed.test.tsx | 13 +- .../components/Failed/Failed.tsx | 11 +- pmm-app/src/pmm-check/CheckPanel.test.tsx | 5 +- pmm-app/src/pmm-check/CheckPanel.tsx | 19 +- .../pmm-check/components/Table/Table.test.tsx | 7 + .../src/pmm-check/components/Table/Table.tsx | 18 +- .../Parts/AlertManager/AlertManager.tsx | 17 +- .../Parts/Diagnostics/Diagnostics.test.tsx | 15 +- .../__snapshots__/Diagnostics.test.tsx.snap | 67 +- .../pmm-settings/Parts/Settings/Settings.tsx | 20 +- .../Parts/UploadSSH/UploadSSH.test.tsx | 16 +- .../Parts/UploadSSH/UploadSSH.tsx | 12 +- .../__snapshots__/UploadSSH.test.tsx.snap | 1009 +++++++++++++++-- .../__snapshots__/panel.test.tsx.snap | 5 - pmm-app/src/pmm-settings/panel.tsx | 2 +- .../FormElement/FormElement.tsx | 2 +- .../FormComponents/Password/Password.test.tsx | 17 +- .../__snapshots__/Password.test.tsx.snap | 33 +- .../FormComponents/TextArea/TextArea.test.tsx | 20 +- .../__snapshots__/TextArea.test.tsx.snap | 35 +- .../components/FormComponents/index.ts | 4 + .../components/helpers/Helpers.test.tsx | 15 +- .../helpers/__mocks__/notification-manager.ts | 6 +- .../__snapshots__/Helpers.test.tsx.snap | 749 +++++++++++- .../components/helpers/api.test.ts | 61 +- .../components/helpers/api.ts | 2 +- .../components/helpers/humanize.test.ts | 14 - .../components/helpers/humanize.ts | 18 - pmm-app/src/react-plugins-deps/styles.scss | 6 +- 34 files changed, 1912 insertions(+), 336 deletions(-) delete mode 100644 pmm-app/src/react-plugins-deps/components/helpers/humanize.test.ts delete mode 100644 pmm-app/src/react-plugins-deps/components/helpers/humanize.ts diff --git a/codecov.yml b/codecov.yml index 44b4e889be..e5647731f5 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,6 @@ +codecov: + token: 3e5d4324-1a64-417c-bdab-3583c34f2911 + coverage: range: "10...80" diff --git a/pmm-app/package-lock.json b/pmm-app/package-lock.json index 575f57729f..700c7420cf 100644 --- a/pmm-app/package-lock.json +++ b/pmm-app/package-lock.json @@ -14583,11 +14583,6 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, - "numeral": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", - "integrity": "sha1-StCAk21EPCVhrtnyGX7//iX05QY=" - }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", diff --git a/pmm-app/package.json b/pmm-app/package.json index 9d33267548..4a831ecdab 100644 --- a/pmm-app/package.json +++ b/pmm-app/package.json @@ -12,8 +12,8 @@ "scripts": { "build": "grafana-toolkit plugin:build", "test": "grafana-toolkit plugin:test", - "jest": "jest", "test:update": "grafana-toolkit plugin:test --updateSnapshot", + "jest": "jest", "coverage": "grafana-toolkit plugin:test --coverage", "dev": "grafana-toolkit plugin:dev", "watch": "grafana-toolkit plugin:dev --watch", @@ -22,7 +22,7 @@ "codecov": "codecov" }, "bin": { - "toolkit": "node_modules/grafana/packages/grafana-toolkit/bin/grafana-toolkit.js" + "toolkit": "node_modules/@grafana/toolkit/bin/grafana-toolkit.dist.js" }, "keywords": [ "percona", @@ -37,10 +37,9 @@ "antd": "^3.24.3", "axios": "^0.19.0", "emotion": "10.0.27", - "final-form": "^4.18.5", + "final-form": "^4.19.1", "history": "^4.10.1", "lodash": "^4.17.15", - "numeral": "^2.0.6", "react": "16.12.0", "react-dom": "16.12.0", "react-final-form": "^6.3.5", @@ -83,7 +82,6 @@ "node-sass": "^4.12.0", "playwright": "^0.12.1", "playwright-video": "^0.13.0", - "react-test-renderer": "^16.12.0", "sass-loader": "^8.0.2", "selenium-standalone": "^6.17.0", "style-loader": "^1.1.4", diff --git a/pmm-app/src/pmm-add-instance-app-panel/AddInstance/AddRemoteInstance/AddRemoteInstance.test.tsx b/pmm-app/src/pmm-add-instance-app-panel/AddInstance/AddRemoteInstance/AddRemoteInstance.test.tsx index ebdf5cab41..3535556eb3 100644 --- a/pmm-app/src/pmm-add-instance-app-panel/AddInstance/AddRemoteInstance/AddRemoteInstance.test.tsx +++ b/pmm-app/src/pmm-add-instance-app-panel/AddInstance/AddRemoteInstance/AddRemoteInstance.test.tsx @@ -1,8 +1,6 @@ import { getInstanceData } from './AddRemoteInstance'; -jest.mock('../../../react-plugins-deps/components/helpers/notification-manager', () => () => ({ - showErrorNotification: () => {}, -})); +jest.mock('../../../react-plugins-deps/components/helpers/notification-manager'); describe('Add remote instance', () => { it('get instance data should return correct one when isRDS is false', () => { diff --git a/pmm-app/src/pmm-check-home/CheckPanel.tsx b/pmm-app/src/pmm-check-home/CheckPanel.tsx index 4cd25f53dd..2db646defc 100644 --- a/pmm-app/src/pmm-check-home/CheckPanel.tsx +++ b/pmm-app/src/pmm-check-home/CheckPanel.tsx @@ -12,6 +12,7 @@ export interface CheckPanelProps extends PanelProps {} export interface CheckPanelState { failedChecks?: FailedChecks; + hasNoAccess: boolean; isSttEnabled: boolean; isLoading: boolean; } @@ -21,6 +22,7 @@ const history = createBrowserHistory(); export class CheckPanel extends PureComponent { state: CheckPanelState = { failedChecks: undefined, + hasNoAccess: false, isSttEnabled: false, isLoading: true, }; @@ -50,6 +52,7 @@ export class CheckPanel extends PureComponent try { const resp = (await CheckService.getSettings()) as Settings; this.setState({ isSttEnabled: !!resp.settings?.stt_enabled }); + this.setState({ hasNoAccess: false }); if (resp.settings?.stt_enabled) { this.fetchAlerts(); } else { @@ -57,17 +60,20 @@ export class CheckPanel extends PureComponent } } catch (err) { this.setState({ isLoading: false }); + if (err.response?.status === 401) { + this.setState({ hasNoAccess: true }); + } console.error(err); } } render() { - const { isSttEnabled, failedChecks, isLoading } = this.state; + const { isSttEnabled, failedChecks, isLoading, hasNoAccess } = this.state; return (
{isLoading && } - {!isLoading && } + {!isLoading && }
); } diff --git a/pmm-app/src/pmm-check-home/components/Failed/Failed.test.tsx b/pmm-app/src/pmm-check-home/components/Failed/Failed.test.tsx index f7b2521b39..691851d2c8 100644 --- a/pmm-app/src/pmm-check-home/components/Failed/Failed.test.tsx +++ b/pmm-app/src/pmm-check-home/components/Failed/Failed.test.tsx @@ -5,7 +5,7 @@ import { Failed } from './Failed'; describe('Failed::', () => { it('should render a sum of total failed checks with severity details', () => { - const root = shallow(); + const root = shallow(); const spans = root.find('div a > span'); expect(spans.at(0).text()).toEqual('1'); @@ -15,16 +15,23 @@ describe('Failed::', () => { }); it('should render 0 when the sum of all checks is zero', () => { - const root = shallow(); + const root = shallow(); expect(root.find('div > span').text()).toEqual('0'); root.unmount(); }); it('should render an inner tooltip component', () => { - const root = shallow(); + const root = shallow(); expect(root.find(Tooltip).length).toEqual(1); root.unmount(); }); + + it('should render a message when the user only has reader access', () => { + const root = shallow(); + + expect(root.find('[data-qa="db-check-panel-no-access"]').text()).toEqual('Insufficient access rights.'); + root.unmount(); + }); }); diff --git a/pmm-app/src/pmm-check-home/components/Failed/Failed.tsx b/pmm-app/src/pmm-check-home/components/Failed/Failed.tsx index 7732c269c3..02dd1f9370 100644 --- a/pmm-app/src/pmm-check-home/components/Failed/Failed.tsx +++ b/pmm-app/src/pmm-check-home/components/Failed/Failed.tsx @@ -9,12 +9,21 @@ import * as styles from './Failed.styles'; interface FailedProps { failed?: FailedChecks; + hasNoAccess: boolean; isSttEnabled: boolean; } -export const Failed: FC = ({ failed = [0, 0, 0], isSttEnabled }) => { +export const Failed: FC = ({ failed = [0, 0, 0], isSttEnabled, hasNoAccess }) => { const sum = useMemo(() => failed.reduce((acc, val) => acc + val, 0), [failed]); + if (hasNoAccess) { + return ( +
+ Insufficient access rights. +
+ ); + } + if (!isSttEnabled) { return (
diff --git a/pmm-app/src/pmm-check/CheckPanel.test.tsx b/pmm-app/src/pmm-check/CheckPanel.test.tsx index 149b2c0193..5b4a6bf640 100644 --- a/pmm-app/src/pmm-check/CheckPanel.test.tsx +++ b/pmm-app/src/pmm-check/CheckPanel.test.tsx @@ -43,7 +43,7 @@ describe('CheckPanel::', () => { await root.instance().fetchAlerts(); wrapper.update(); - expect(root.state('dataSource')).toEqual(activeCheckStub); + expect(root.state().dataSource).toEqual(activeCheckStub); expect(root.state().isLoading).toEqual(false); expect(root.state().isSttEnabled).toEqual(false); @@ -64,7 +64,8 @@ describe('CheckPanel::', () => { root.setState({ isLoading: false }); wrapper.update(); - expect(root.state('isLoading')).toEqual(false); + expect(root.state().isLoading).toEqual(false); + expect(root.state().hasNoAccess).toEqual(false); const table = wrapper.find('[data-qa="db-check-panel"]').find(Table); // Check if the table is rendered, the rest is tested by the Table itself diff --git a/pmm-app/src/pmm-check/CheckPanel.tsx b/pmm-app/src/pmm-check/CheckPanel.tsx index 8f3c604b97..0602da9fe3 100644 --- a/pmm-app/src/pmm-check/CheckPanel.tsx +++ b/pmm-app/src/pmm-check/CheckPanel.tsx @@ -13,6 +13,7 @@ export interface CheckPanelProps extends PanelProps {} export interface CheckPanelState { dataSource?: ActiveCheck[]; + hasNoAccess: boolean; isLoading: boolean; isSttEnabled: boolean; } @@ -22,6 +23,7 @@ const history = createBrowserHistory(); export class CheckPanel extends PureComponent { state = { dataSource: undefined, + hasNoAccess: false, isLoading: true, isSttEnabled: false, }; @@ -51,14 +53,19 @@ export class CheckPanel extends PureComponent try { const resp = (await CheckService.getSettings()) as Settings; this.setState({ isSttEnabled: !!resp.settings?.stt_enabled }); + this.setState({ hasNoAccess: false }); + if (resp.settings?.stt_enabled) { this.fetchAlerts(); } else { this.setState({ isLoading: false }); } } catch (err) { - console.error(err); this.setState({ isLoading: false }); + if (err.response?.status === 401) { + this.setState({ hasNoAccess: true }); + } + console.error(err); } } @@ -66,7 +73,7 @@ export class CheckPanel extends PureComponent const { options: { title }, } = this.props; - const { dataSource, isSttEnabled, isLoading } = this.state; + const { dataSource, isSttEnabled, isLoading, hasNoAccess } = this.state; return (
@@ -76,7 +83,13 @@ export class CheckPanel extends PureComponent
)} {!isLoading && ( - +
)} ); diff --git a/pmm-app/src/pmm-check/components/Table/Table.test.tsx b/pmm-app/src/pmm-check/components/Table/Table.test.tsx index 8058263d91..9fb966d478 100644 --- a/pmm-app/src/pmm-check/components/Table/Table.test.tsx +++ b/pmm-app/src/pmm-check/components/Table/Table.test.tsx @@ -52,4 +52,11 @@ describe('Table::', () => { expect(table.find(TableHeader).length).toEqual(1); expect(table.find(TableBody).length).toEqual(1); }); + + it('should render the table with a message when the user only has reader access', () => { + const root = shallow(
); + + const empty = root.find('[data-qa="db-check-panel-no-access"]'); + expect(empty.text()).toEqual('Insufficient access rights.'); + }); }); diff --git a/pmm-app/src/pmm-check/components/Table/Table.tsx b/pmm-app/src/pmm-check/components/Table/Table.tsx index 21878b3a0a..e747eedb1b 100644 --- a/pmm-app/src/pmm-check/components/Table/Table.tsx +++ b/pmm-app/src/pmm-check/components/Table/Table.tsx @@ -11,14 +11,30 @@ interface TableProps { caption?: string; columns: Column[]; data?: ActiveCheck[]; + hasNoAccess?: boolean; isSttEnabled: boolean; } -export const Table: FC = ({ caption, columns, data = [], isSttEnabled }) => { +export const Table: FC = ({ caption, columns, data = [], isSttEnabled, hasNoAccess = false }) => { const theme = useTheme(); const styles = getStyles(theme); const isEmpty = !data.length; + if (hasNoAccess) { + return ( + <> +
+ {caption} +
+
+
+ Insufficient access rights. +
+
+ + ); + } + return ( <> {caption && ( diff --git a/pmm-app/src/pmm-settings/Parts/AlertManager/AlertManager.tsx b/pmm-app/src/pmm-settings/Parts/AlertManager/AlertManager.tsx index b76d8652bb..599ec06075 100644 --- a/pmm-app/src/pmm-settings/Parts/AlertManager/AlertManager.tsx +++ b/pmm-app/src/pmm-settings/Parts/AlertManager/AlertManager.tsx @@ -1,13 +1,10 @@ -import { InputField } from '../../../react-plugins-deps/components/FormComponents/Input/Input'; -import { TextAreaField } from '../../../react-plugins-deps/components/FormComponents/TextArea/TextArea'; import React, { ReactElement, useEffect, useState } from 'react'; -import ButtonElement from '../../../react-plugins-deps/components/FormComponents/Button/Button'; import { Form as FormFinal } from 'react-final-form'; -import { SettingsService } from '../../Settings.service'; -import { showSuccessNotification } from '../../../react-plugins-deps/components/helpers/notification-manager'; -import { FormElement } from '../../../react-plugins-deps/components/FormComponents/FormElement/FormElement'; -import { PluginTooltip } from '../../../react-plugins-deps/components/helpers/Helpers'; import { css } from 'emotion'; +import { Button, FormElement, InputField, TextAreaField } from 'react-plugins-deps/components/FormComponents'; +import { showSuccessNotification } from 'react-plugins-deps/components/helpers/notification-manager'; +import { PluginTooltip } from 'react-plugins-deps/components/helpers/Helpers'; +import { SettingsService } from '../../Settings.service'; import { GUI_DOC_URL } from '../../panel.constants'; interface AlertManagerSettingsInterface { @@ -61,7 +58,7 @@ const AlertManager = props => {
<> { } /> { } alignLabel="top" /> - + + + + `; diff --git a/pmm-app/src/pmm-settings/Parts/Settings/Settings.tsx b/pmm-app/src/pmm-settings/Parts/Settings/Settings.tsx index 24cf615082..9db45e4354 100644 --- a/pmm-app/src/pmm-settings/Parts/Settings/Settings.tsx +++ b/pmm-app/src/pmm-settings/Parts/Settings/Settings.tsx @@ -3,6 +3,7 @@ import { Collapse } from 'antd'; import { Form as FormFinal } from 'react-final-form'; import { css } from 'emotion'; import { showSuccessNotification, PluginTooltip } from 'react-plugins-deps/components/helpers'; +import Validators from 'react-plugins-deps/components/validators/validators'; import { Button, FormElement, @@ -10,7 +11,6 @@ import { SliderField, ToggleField, } from 'react-plugins-deps/components/FormComponents'; -import Validators from '../../../react-plugins-deps/components/validators/validators'; import { SettingsService } from '../../Settings.service'; import { GUI_DOC_URL } from '../../panel.constants'; import './Settings.scss'; @@ -154,11 +154,13 @@ const SettingsPart = props => { }); }, [settings]); + const { values } = form.getState(); + return (
<> { > { } /> { text="Option to send usage data back to Percona to let us make our product better" /> } - element={ - - } + element={} /> { text="Enable Security Threat Tool and get updated checks from Percona" /> } - element={ - - } + element={} /> diff --git a/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.test.tsx b/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.test.tsx index 58a02520bf..1bf6ed592e 100644 --- a/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.test.tsx +++ b/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.test.tsx @@ -1,18 +1,20 @@ import React from 'react'; -import renderer from 'react-test-renderer'; +import { mount } from 'enzyme'; import UploadSSH from './UploadSSH'; -jest.mock('../../../react-plugins-deps/components/helpers/notification-manager', () => () => ({})); -describe('Settings Part test', () => { - it('Upload SSH key renders correct without props', () => { - const component = renderer.create( +jest.mock('../../../react-plugins-deps/components/helpers/notification-manager'); + +describe('UploadSSH', () => { + it('Upload SSH key renders without props', () => { + const root = mount( ); - const tree = component.toJSON(); - expect(tree).toMatchSnapshot(); + + expect(root).toMatchSnapshot(); + root.unmount(); }); }); diff --git a/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.tsx b/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.tsx index 947ed0c874..3f7721f869 100644 --- a/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.tsx +++ b/pmm-app/src/pmm-settings/Parts/UploadSSH/UploadSSH.tsx @@ -1,11 +1,9 @@ -import { PluginTooltip } from '../../../react-plugins-deps/components/helpers/Helpers'; -import { TextAreaField } from '../../../react-plugins-deps/components/FormComponents/TextArea/TextArea'; import React, { ReactNode, useEffect, useState } from 'react'; -import ButtonElement from '../../../react-plugins-deps/components/FormComponents/Button/Button'; import { Form as FormFinal } from 'react-final-form'; +import { PluginTooltip } from 'react-plugins-deps/components/helpers/Helpers'; +import { Button, FormElement, TextAreaField } from 'react-plugins-deps/components/FormComponents'; +import { showSuccessNotification } from 'react-plugins-deps/components/helpers/notification-manager'; import { SettingsService } from '../../Settings.service'; -import { showSuccessNotification } from '../../../react-plugins-deps/components/helpers/notification-manager'; -import { FormElement } from '../../../react-plugins-deps/components/FormComponents/FormElement/FormElement'; import { GUI_DOC_URL } from '../../panel.constants'; interface UploadSSHInterface { @@ -35,7 +33,7 @@ const UploadSSH = ({ settings }) => { <> { } alignLabel="top" /> - +