From: Grégoire Aubert Date: Thu, 8 Feb 2018 14:38:53 +0000 (+0100) Subject: SONAR-10347 Create a BoxedGroupAccordion component and use it in system page X-Git-Tag: 7.5~1611 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=87ca6669cd77f57d0621c70e53adaa42350ff128;p=sonarqube.git SONAR-10347 Create a BoxedGroupAccordion component and use it in system page --- diff --git a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/SystemInfoPage.java b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/SystemInfoPage.java index 9c9802e7e7f..857153eb517 100644 --- a/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/SystemInfoPage.java +++ b/server/sonar-qa-util/src/main/java/org/sonarqube/qa/util/pageobjects/SystemInfoPage.java @@ -21,26 +21,28 @@ package org.sonarqube.qa.util.pageobjects; import com.codeborne.selenide.CollectionCondition; import com.codeborne.selenide.Condition; -import com.codeborne.selenide.Selenide; +import com.codeborne.selenide.ElementsCollection; import com.codeborne.selenide.SelenideElement; +import static com.codeborne.selenide.Selenide.$; +import static com.codeborne.selenide.Selenide.$$; + public class SystemInfoPage { public SystemInfoPage() { - Selenide.$(".page-title").should(Condition.exist).shouldHave(Condition.text("System Info")); - } - - public SystemInfoPage shouldHaveCard(String title) { - Selenide.$$(".system-info-health-card-title").find(Condition.text(title)).should(Condition.exist); - return this; + $(".page-title").should(Condition.exist).shouldHave(Condition.text("System Info")); } public SystemInfoPage shouldHaveCards(String... titles) { - Selenide.$$(".system-info-health-card-title").shouldHave(CollectionCondition.texts(titles)); + getHealthCards().shouldHave(CollectionCondition.texts(titles)); return this; } public SystemInfoPageItem getCardItem(String card) { - SelenideElement cardTitle = Selenide.$$(".system-info-health-card-title").find(Condition.text(card)).should(Condition.exist); + SelenideElement cardTitle = getHealthCards().find(Condition.text(card)).should(Condition.exist); return new SystemInfoPageItem(cardTitle.parent().parent()); } + + private static ElementsCollection getHealthCards() { + return $$(".boxed-group-accordion-title"); + } } diff --git a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css index 68e97474d38..49aa03123b6 100644 --- a/server/sonar-web/src/main/js/app/styles/components/boxed-group.css +++ b/server/sonar-web/src/main/js/app/styles/components/boxed-group.css @@ -26,19 +26,19 @@ .boxed-group > h2 { line-height: var(--controlHeight); - padding: 15px 20px 0; + padding: calc(2 * var(--gridSize)) 20px 0; } .boxed-group hr { height: 0; border-top: 1px solid var(--gray94); - margin: 15px -20px; + margin: calc(2 * var(--gridSize)) -20px; } .boxed-group-header { position: relative; z-index: 10; - padding: 15px 20px 0; + padding: calc(2 * var(--gridSize)) 20px 0; } .boxed-group-header > h2 { @@ -56,12 +56,12 @@ position: relative; z-index: 12; float: right; - margin-top: 15px; + margin-top: calc(2 * var(--gridSize)); margin-right: 20px; } .boxed-group-inner { - padding: 15px 20px; + padding: calc(2 * var(--gridSize)) 20px; } .boxed-group-inner:empty { @@ -69,12 +69,45 @@ } .boxed-group-list { - margin-top: -8px; - margin-bottom: -8px; + margin-top: - var(--gridSize); + margin-bottom: - var(--gridSize); } .boxed-group-list > li { margin-left: -20px; margin-right: -20px; - padding: 8px 20px; + padding: var(--gridSize) 20px; +} + +.boxed-group-accordion { + margin-bottom: var(--gridSize); + transition: border-color 0.3s ease; +} + +.boxed-group-accordion:not(.no-hover):hover { + border-color: var(--blue); +} + +.boxed-group-accordion:not(.no-hover):hover .boxed-group-accordion-title { + color: var(--blue); +} + +.boxed-group-accordion .boxed-group-header { + cursor: pointer; + padding-bottom: calc(2 * var(--gridSize)); +} + +.boxed-group-accordion .boxed-group-header > .alert { + display: inline-block; + margin-bottom: -6px; + margin-top: -6px; +} + +.boxed-group-accordion .boxed-group-inner { + padding-top: 0; +} + +.boxed-group-accordion-title { + font-weight: bold; + transition: color 0.3s ease; } diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx index dec9d88310f..1d7ae6d3f8c 100644 --- a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx @@ -41,7 +41,7 @@ interface Props { export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) { const mainCardName = 'System'; return ( - + ); } diff --git a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx index ba09a11934a..ecdb45fe85c 100644 --- a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx @@ -38,7 +38,7 @@ interface Props { export default function StandAloneSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) { const mainCardName = 'System'; return ( - + ); } diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap index ca216f72e04..0e31e22e4be 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should support more than two nodes 1`] = ` - + `; diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap index e5832de513c..0d7912636bf 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly 1`] = ` - + `; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx index 105c8002bcd..f042eed7de3 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx @@ -18,11 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import * as classNames from 'classnames'; import { map } from 'lodash'; import HealthItem from './HealthItem'; import Section from './Section'; -import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon'; +import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion'; import { HealthType, SysValueObject } from '../../../../api/system'; import { LOGS_LEVELS, groupSections, getLogsLevel } from '../../utils'; import { translate } from '../../../../helpers/l10n'; @@ -37,34 +36,27 @@ interface Props { sysInfoData: SysValueObject; } -interface State { - hoveringDetail: boolean; -} - -export default class HealthCard extends React.PureComponent { - state: State = { hoveringDetail: false }; - - handleClick = () => this.props.onClick(this.props.name); - onDetailEnter = () => this.setState({ hoveringDetail: true }); - onDetailLeave = () => this.setState({ hoveringDetail: false }); - - render() { - const { health, open, sysInfoData } = this.props; - const { mainSection, sections } = groupSections(sysInfoData); - const showFields = open && mainSection && Object.keys(mainSection).length > 0; - const showSections = open && sections; - const logLevel = getLogsLevel(sysInfoData); - const showLogLevelWarning = logLevel && logLevel !== LOGS_LEVELS[0]; - return ( -
  • -
    - - - {this.props.name} - +export default function HealthCard({ + biggerHealth, + health, + healthCauses, + onClick, + open, + name, + sysInfoData +}: Props) { + const { mainSection, sections } = groupSections(sysInfoData); + const showFields = open && mainSection && Object.keys(mainSection).length > 0; + const showSections = open && sections; + const logLevel = getLogsLevel(sysInfoData); + const showLogLevelWarning = logLevel && logLevel !== LOGS_LEVELS[0]; + return ( + ( + <> {showLogLevelWarning && ( {translate('system.log_level.warning.short')} @@ -72,25 +64,19 @@ export default class HealthCard extends React.PureComponent { )} {health && ( )} -
    - {open && ( -
    - {showFields &&
    } - {showSections && - map(sections, (section, name) =>
    )} -
    - )} -
  • - ); - } + + )} + title={name}> + {showFields &&
    } + {showSections && + map(sections, (section, name) =>
    )} + + ); } diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx index abe3f8b002b..08ea6b5f66d 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx @@ -20,22 +20,10 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import HealthCard from '../HealthCard'; -import { click } from '../../../../../helpers/testUtils'; import { HealthType } from '../../../../../api/system'; it('should render correctly', () => { - expect(getShallowWrapper()).toMatchSnapshot(); -}); - -it('should display the sysinfo detail', () => { - expect(getShallowWrapper({ biggerHealth: true, open: true })).toMatchSnapshot(); -}); - -it('should show the sysinfo detail when the card is clicked', () => { - const onClick = jest.fn(); - click(getShallowWrapper({ onClick }).find('.boxed-group-header')); - expect(onClick).toBeCalled(); - expect(onClick).toBeCalledWith('Foobar'); + expect(getWrapper()).toMatchSnapshot(); }); it('should show a main section and multiple sub sections', () => { @@ -45,16 +33,18 @@ it('should show a main section and multiple sub sections', () => { Database: { db: 'test' }, Elasticseach: { Elastic: 'search' } }; - expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot(); + expect(getWrapper({ open: true, sysInfoData })).toMatchSnapshot(); }); it('should display the log level alert', () => { expect( - getShallowWrapper({ sysInfoData: { 'Logs Level': 'DEBUG' } }).find('.alert') + getWrapper({ sysInfoData: { 'Logs Level': 'DEBUG' } }) + .dive() + .find('.alert') ).toMatchSnapshot(); }); -function getShallowWrapper(props = {}) { +function getWrapper(props = {}) { return shallow( `; -exports[`should display the sysinfo detail 1`] = ` -
  • -
    - - - Foobar - - -
    -
    -
  • -`; - exports[`should render correctly 1`] = ` -
  • -
    - - - Foobar - - -
    -
  • + `; exports[`should show a main section and multiple sub sections 1`] = ` -
  • -
    - - - Foobar - - -
    -
    -
    -
    +
    -
    +
    -
    -
  • + } + key="Elasticseach" + name="Elasticseach" + /> +
    `; diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css index c6c4ad4afb5..3c332eaa7c1 100644 --- a/server/sonar-web/src/main/js/apps/system/styles.css +++ b/server/sonar-web/src/main/js/apps/system/styles.css @@ -22,38 +22,6 @@ margin-bottom: 16px; } -.system-info-health-card { - margin-bottom: 8px; - transition: border-color 0.3s ease; -} - -.system-info-health-card:not(.no-hover):hover { - border-color: var(--blue); -} - -.system-info-health-card:not(.no-hover):hover .system-info-health-card-title { - color: var(--blue); -} - -.system-info-health-card .boxed-group-header { - cursor: pointer; - padding-bottom: 15px; -} - -.system-info-health-card .boxed-group-header > .alert { - display: inline-block; - margin-bottom: -6px; - margin-top: -6px; -} - -.system-info-health-card .boxed-group-inner { - padding-top: 0; -} - -.system-info-health-card-title { - font-weight: bold; -} - .system-info-health-info { margin-top: -12px; } diff --git a/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx b/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx new file mode 100644 index 00000000000..63129cc5b7f --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/BoxedGroupAccordion.tsx @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import OpenCloseIcon from '../icons-components/OpenCloseIcon'; + +interface Props { + children: React.ReactNode; + className?: string; + data?: string; + onClick: (data?: string) => void; + open: boolean; + renderHeader?: () => React.ReactNode; + title: React.ReactNode; +} + +interface State { + hoveringInner: boolean; +} + +export default class BoxedGroupAccordion extends React.PureComponent { + state: State = { hoveringInner: false }; + + handleClick = () => { + this.props.onClick(this.props.data); + }; + + onDetailEnter = () => { + this.setState({ hoveringInner: true }); + }; + + onDetailLeave = () => { + this.setState({ hoveringInner: false }); + }; + + render() { + const { className, open, renderHeader, title } = this.props; + return ( +
    +
    + + + {title} + + {renderHeader && renderHeader()} +
    + {open && ( +
    + {this.props.children} +
    + )} +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx new file mode 100644 index 00000000000..8101724be4b --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedGroupAccordion-test.tsx @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * 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 GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { click } from '../../../helpers/testUtils'; +import BoxedGroupAccordion from '../BoxedGroupAccordion'; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +it('should show the inner content after a click', () => { + const onClick = jest.fn(); + const wrapper = getWrapper({ onClick }); + click(wrapper.find('.boxed-group-header')); + + expect(onClick).lastCalledWith('foo'); + wrapper.setProps({ open: true }); + + expect(wrapper.find('.boxed-group-inner').exists()).toBeTruthy(); +}); + +function getWrapper(props = {}) { + return shallow( + {}} + open={false} + renderHeader={() =>
    header content
    } + title="Foo" + {...props}> +
    inner content
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap new file mode 100644 index 00000000000..0c7b74b79d9 --- /dev/null +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedGroupAccordion-test.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
    +
    + + + Foo + +
    + header content +
    +
    +
    +`;