From 5a1bf49ac17f4936a09efc0adf26b7622c7cbd4a Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Tue, 10 Oct 2017 14:37:26 +0200 Subject: [PATCH] SONAR-9876 Improve facet badges --- .../component-measures/sidebar/DomainFacet.js | 13 +++- .../__snapshots__/DomainFacet-test.js.snap | 8 ++- .../js/apps/issues/sidebar/AssigneeFacet.js | 13 +++- .../js/apps/issues/sidebar/AuthorFacet.js | 2 +- .../apps/issues/sidebar/CreationDateFacet.js | 30 ++++++++- .../js/apps/issues/sidebar/DirectoryFacet.js | 13 ++-- .../main/js/apps/issues/sidebar/FileFacet.js | 13 ++-- .../js/apps/issues/sidebar/LanguageFacet.js | 3 +- .../js/apps/issues/sidebar/ModuleFacet.js | 12 ++-- .../js/apps/issues/sidebar/ProjectFacet.js | 8 ++- .../js/apps/issues/sidebar/ResolutionFacet.js | 3 +- .../main/js/apps/issues/sidebar/RuleFacet.js | 3 +- .../js/apps/issues/sidebar/SeverityFacet.js | 3 +- .../js/apps/issues/sidebar/StatusFacet.js | 3 +- .../main/js/apps/issues/sidebar/TagFacet.js | 2 +- .../main/js/apps/issues/sidebar/TypeFacet.js | 3 +- .../__snapshots__/AssigneeFacet-test.js.snap | 16 +++-- .../main/js/components/facet/FacetHeader.js | 40 ++++++----- .../facet/__tests__/FacetHeader-test.js | 12 +++- .../__snapshots__/FacetHeader-test.js.snap | 66 ++++++++++++++----- .../less/components/search-navigator.less | 22 ++++++- .../resources/org/sonar/l10n/core.properties | 1 + 22 files changed, 215 insertions(+), 74 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js index 4a0e1620798..b59039f280e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/DomainFacet.js @@ -63,6 +63,15 @@ export default class DomainFacet extends React.PureComponent { return measureSelected || overviewSelected; }; + getValues = () => { + const { domain, selected } = this.props; + const measureSelected = domain.measures.find(measure => measure.metric.key === selected); + const overviewSelected = domain.name === selected && hasBubbleChart(domain.name); + return measureSelected + ? [getLocalizedMetricName(measureSelected.metric)] + : overviewSelected ? [translate('component_measures.domain_overview')] : []; + }; + renderItemFacetStat = (item /*: MeasureEnhanced */) => hasFacetStat(item.metric.key) ? : null; @@ -121,7 +130,7 @@ export default class DomainFacet extends React.PureComponent { }; render() { - const { domain, selected } = this.props; + const { domain } = this.props; const helper = `component_measures.domain_facets.${domain.name}.help`; const translatedHelper = translate(helper); return ( @@ -131,7 +140,7 @@ export default class DomainFacet extends React.PureComponent { name={getLocalizedMetricDomain(domain.name)} onClick={this.handleHeaderClick} open={this.props.open} - values={this.hasFacetSelected(domain, domain.measures, selected) ? 1 : 0} + values={this.getValues()} /> {this.props.open && ( diff --git a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap index 422bc79790f..fd5ddec46a2 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap +++ b/server/sonar-web/src/main/js/apps/component-measures/sidebar/__tests__/__snapshots__/DomainFacet-test.js.snap @@ -6,7 +6,7 @@ exports[`should display facet item list 1`] = ` name="Reliability" onClick={[Function]} open={true} - values={0} + values={Array []} /> { + const user = this.props.referencedUsers[assignee]; + return user ? user.name : assignee; + }); + if (!this.props.assigned) { + values.push(translate('unassigned')); + } + return values; + } + renderOption = (option /*: { avatar: string, label: string } */) => { return ( @@ -190,7 +201,7 @@ export default class AssigneeFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.assignees.length + (this.props.assigned ? 0 : 1)} + values={this.getValues()} /> {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.js index ee9fce637fd..27d460a62c7 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/AuthorFacet.js @@ -101,7 +101,7 @@ export default class AuthorFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.authors.length} + values={this.props.authors} /> {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js index 78edc628f71..461d3405d14 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/CreationDateFacet.js @@ -119,6 +119,34 @@ export default class CreationDateFacet extends React.PureComponent { handleLeakPeriodClick = () => this.resetTo({ sinceLeakPeriod: true }); + getValues() { + const { createdAfter, createdAt, createdBefore, createdInLast, sinceLeakPeriod } = this.props; + const { formatDate } = this.context.intl; + const values = []; + if (createdAfter) { + values.push(formatDate(createdAfter, longFormatterOption)); + } + if (createdAt) { + values.push(formatDate(createdAt, longFormatterOption)); + } + if (createdBefore) { + values.push(formatDate(createdBefore, longFormatterOption)); + } + if (createdInLast === '1w') { + values.push(translate('issues.facet.createdAt.last_week')); + } + if (createdInLast === '1m') { + values.push(translate('issues.facet.createdAt.last_month')); + } + if (createdInLast === '1y') { + values.push(translate('issues.facet.createdAt.last_year')); + } + if (sinceLeakPeriod) { + values.push(translate('issues.leak_period')); + } + return values; + } + renderBarChart() { const { createdBefore, stats } = this.props; @@ -286,7 +314,7 @@ export default class CreationDateFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.hasValue() ? 1 : 0} + values={this.getValues()} /> {this.props.open && this.renderInner()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.js index c860c1a0d4b..98f9dd3771a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/DirectoryFacet.js @@ -26,6 +26,7 @@ import FacetItem from '../../../components/facet/FacetItem'; import FacetItemsList from '../../../components/facet/FacetItemsList'; import QualifierIcon from '../../../components/shared/QualifierIcon'; import { translate } from '../../../helpers/l10n'; +import { collapsePath } from '../../../helpers/path'; import { formatFacetStat } from '../utils'; /*:: import type { ReferencedComponent } from '../utils'; */ @@ -74,17 +75,10 @@ export default class DirectoryFacet extends React.PureComponent { } renderName(directory /*: string */) /*: React.Element<*> | string */ { - // `referencedComponents` are indexed by uuid - // so we have to browse them all to find a matching one - const { referencedComponents } = this.props; - const uuid = Object.keys(referencedComponents).find( - uuid => referencedComponents[uuid].key === directory - ); - const name = uuid ? referencedComponents[uuid].name : directory; return ( - {name} + {directory} ); } @@ -115,6 +109,7 @@ export default class DirectoryFacet extends React.PureComponent { } render() { + const values = this.props.directories.map(dir => collapsePath(dir)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.js index b3b85d83026..08098cb4c39 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/FileFacet.js @@ -72,11 +72,13 @@ export default class FileFacet extends React.PureComponent { return stats ? stats[file] : null; } - renderName(file /*: string */) /*: React.Element<*> | string */ { + getFileName(file /*: string */) { const { referencedComponents } = this.props; - const name = referencedComponents[file] - ? collapsePath(referencedComponents[file].path, 15) - : file; + return referencedComponents[file] ? collapsePath(referencedComponents[file].path, 15) : file; + } + + renderName(file /*: string */) /*: React.Element<*> | string */ { + const name = this.getFileName(file); return ( @@ -111,6 +113,7 @@ export default class FileFacet extends React.PureComponent { } render() { + const values = this.props.files.map(file => this.getFileName(file)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.js index 5250e30be02..6e2d9a3063c 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/LanguageFacet.js @@ -115,6 +115,7 @@ export default class LanguageFacet extends React.PureComponent { } render() { + const values = this.props.languages.map(language => this.getLanguageName(language)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.js index 7c787c479ec..9c07eb03c9e 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ModuleFacet.js @@ -71,13 +71,16 @@ export default class ModuleFacet extends React.PureComponent { return stats ? stats[module] : null; } - renderName(module /*: string */) /*: React.Element<*> | string */ { + getModuleName(module /*: string */) { const { referencedComponents } = this.props; - const name = referencedComponents[module] ? referencedComponents[module].name : module; + return referencedComponents[module] ? referencedComponents[module].name : module; + } + + renderName(module /*: string */) /*: React.Element<*> | string */ { return ( - {name} + {this.getModuleName(module)} ); } @@ -108,6 +111,7 @@ export default class ModuleFacet extends React.PureComponent { } render() { + const values = this.props.modules.map(module => this.getModuleName(module)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js index 9bb838768b8..e3488cb49ff 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ProjectFacet.js @@ -106,6 +106,11 @@ export default class ProjectFacet extends React.PureComponent { return stats ? stats[project] : null; } + getProjectName(project /*: string */) { + const { referencedComponents } = this.props; + return referencedComponents[project] ? referencedComponents[project].name : project; + } + renderName(project /*: string */) /*: React.Element<*> | string */ { const { organization, referencedComponents } = this.props; return referencedComponents[project] ? ( @@ -174,6 +179,7 @@ export default class ProjectFacet extends React.PureComponent { } render() { + const values = this.props.projects.map(project => this.getProjectName(project)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.js index d3732a264ab..871ec28576a 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.js @@ -105,6 +105,7 @@ export default class ResolutionFacet extends React.PureComponent { render() { const resolutions = ['', 'FIXED', 'FALSE-POSITIVE', 'WONTFIX', 'REMOVED']; + const values = this.props.resolutions.map(resolution => this.getFacetItemName(resolution)); return ( @@ -113,7 +114,7 @@ export default class ResolutionFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.resolutions.length} + values={values} /> {this.props.open && {resolutions.map(this.renderItem)}} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.js index 120cf64f94b..e5ac931e3e4 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/RuleFacet.js @@ -127,6 +127,7 @@ export default class RuleFacet extends React.PureComponent { } render() { + const values = this.props.rules.map(rule => this.getRuleName(rule)); return ( {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.js index 2da4dece8af..d4780a24aec 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/SeverityFacet.js @@ -89,6 +89,7 @@ export default class SeverityFacet extends React.PureComponent { render() { const severities = ['BLOCKER', 'MINOR', 'CRITICAL', 'INFO', 'MAJOR']; + const values = this.props.severities.map(severity => translate('severity', severity)); return ( @@ -97,7 +98,7 @@ export default class SeverityFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.severities.length} + values={values} /> {this.props.open && {severities.map(this.renderItem)}} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.js index 4c4ac2cc1f6..c5c45892b98 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/StatusFacet.js @@ -96,6 +96,7 @@ export default class StatusFacet extends React.PureComponent { render() { const statuses = ['OPEN', 'RESOLVED', 'REOPENED', 'CLOSED', 'CONFIRMED']; + const values = this.props.statuses.map(status => translate('issue.status', status)); return ( @@ -104,7 +105,7 @@ export default class StatusFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.statuses.length} + values={values} /> {this.props.open && {statuses.map(this.renderItem)}} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.js index a42d19f5287..31c25943ec2 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/TagFacet.js @@ -138,7 +138,7 @@ export default class TagFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.tags.length} + values={this.props.tags} /> {this.props.open && this.renderList()} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.js b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.js index aafbf49e471..8ad56108511 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.js +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.js @@ -92,6 +92,7 @@ export default class TypeFacet extends React.PureComponent { render() { const types = ['BUG', 'VULNERABILITY', 'CODE_SMELL']; + const values = this.props.types.map(type => translate('issue.type', type)); return ( @@ -100,7 +101,7 @@ export default class TypeFacet extends React.PureComponent { onClear={this.handleClear} onClick={this.handleHeaderClick} open={this.props.open} - values={this.props.types.length} + values={values} /> {this.props.open && {types.map(this.renderItem)}} diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.js.snap b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.js.snap index 08badebe355..d6d4ef6ac7b 100644 --- a/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.js.snap +++ b/server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.js.snap @@ -7,7 +7,7 @@ exports[`should render 1`] = ` onClear={[Function]} onClick={[Function]} open={true} - values={0} + values={Array []} /> `; @@ -87,7 +87,11 @@ exports[`should select unassigned 1`] = ` onClear={[Function]} onClick={[Function]} open={true} - values={1} + values={ + Array [ + "unassigned", + ] + } /> void, onClick?: () => void, open: boolean, - values?: number + values?: Array |}; */ @@ -73,27 +72,25 @@ export default class FacetHeader extends React.PureComponent { } renderValueIndicator() { - if (this.props.open || !this.props.values) { + const { values } = this.props; + if (this.props.open || !values || !values.length) { return null; } + const value = + values.length === 1 ? values[0] : translateWithParameters('x_selected', values.length); return ( - {this.props.values} + + {value} + ); } render() { - const showClearButton /*: boolean */ = !!this.props.values && this.props.onClear != null; + const showClearButton = + this.props.values != null && this.props.values.length > 0 && this.props.onClear != null; return ( -
- {showClearButton && ( - - )} - +
{this.props.onClick ? ( @@ -101,7 +98,6 @@ export default class FacetHeader extends React.PureComponent { {this.props.name} {this.renderHelper()} - {this.renderValueIndicator()} ) : ( @@ -109,6 +105,18 @@ export default class FacetHeader extends React.PureComponent { {this.renderHelper()} )} + + + {this.renderValueIndicator()} + + + {showClearButton && ( + + )}
); } diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/FacetHeader-test.js b/server/sonar-web/src/main/js/components/facet/__tests__/FacetHeader-test.js index aaa674c1926..f6d3907f4d6 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/FacetHeader-test.js +++ b/server/sonar-web/src/main/js/components/facet/__tests__/FacetHeader-test.js @@ -25,7 +25,7 @@ import FacetHeader from '../FacetHeader'; it('should render open facet with value', () => { expect( - shallow() + shallow() ).toMatchSnapshot(); }); @@ -35,7 +35,7 @@ it('should render open facet without value', () => { it('should render closed facet with value', () => { expect( - shallow() + shallow() ).toMatchSnapshot(); }); @@ -57,7 +57,13 @@ it('should call onClick', () => { it('should clear', () => { const onClear = jest.fn(); const wrapper = shallow( - + ); expect(wrapper).toMatchSnapshot(); click(wrapper.find('.button-red')); diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap index dc22f069760..dad6166c959 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap +++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap @@ -1,13 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should clear 1`] = ` -
- +
@@ -21,17 +17,30 @@ exports[`should clear 1`] = ` /> foo + + - 3 + x_selected.3 +
`; exports[`should render closed facet with value 1`] = ` -
+
@@ -45,17 +54,24 @@ exports[`should render closed facet with value 1`] = ` /> foo + + - 1 + foo
`; exports[`should render closed facet without value 1`] = ` -
+
@@ -70,11 +86,16 @@ exports[`should render closed facet without value 1`] = ` foo +
`; exports[`should render open facet with value 1`] = ` -
+
@@ -89,11 +110,16 @@ exports[`should render open facet with value 1`] = ` foo +
`; exports[`should render open facet without value 1`] = ` -
+
@@ -108,15 +134,23 @@ exports[`should render open facet without value 1`] = ` foo +
`; exports[`should render without link 1`] = ` -
+
foo +
`; diff --git a/server/sonar-web/src/main/less/components/search-navigator.less b/server/sonar-web/src/main/less/components/search-navigator.less index e6c37014795..dcd88bc69d7 100644 --- a/server/sonar-web/src/main/less/components/search-navigator.less +++ b/server/sonar-web/src/main/less/components/search-navigator.less @@ -360,9 +360,12 @@ .search-navigator-facet-header { display: block; + flex-shrink: 0; padding: 8px 0; color: @baseFontColor; font-weight: 600; + overflow: hidden; + white-space: nowrap; & > a { border-bottom: none; @@ -378,9 +381,24 @@ } } +.search-navigator-facet-header-value { + display: block; + padding: 8px 0; + overflow: hidden; +} + +.search-navigator-facet-header-value > .badge { + display: block; +} + .search-navigator-facet-header-button { - float: right; - margin-top: 6px; + flex-shrink: 0; + margin-left: auto; +} + +.search-navigator-facet-header-wrapper { + display: flex; + align-items: center; } .search-navigator-facet-list { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 5642477be12..8f93ac535a5 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -142,6 +142,7 @@ set=Set severity=Severity shared=Shared x_show={0} shown +x_selected={0} selected x_of_y_shown={0} of {1} shown size=Size status=Status -- 2.39.5