Quellcode durchsuchen

rewrite projects app

tags/6.6-RC1
Stas Vilchik vor 6 Jahren
Ursprung
Commit
361d9c3e6c
100 geänderte Dateien mit 2639 neuen und 1306 gelöschten Zeilen
  1. 1
    1
      server/sonar-web/src/main/js/app/components/search/SearchResult.js
  2. 0
    1
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap
  3. 40
    0
      server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
  4. 42
    41
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  5. 1
    2
      server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts
  6. 22
    28
      server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx
  7. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx
  8. 8
    15
      server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx
  9. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx
  10. 2
    3
      server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts
  11. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx
  12. 41
    52
      server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx
  13. 2
    2
      server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts
  14. 12
    17
      server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx
  15. 22
    36
      server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx
  16. 18
    18
      server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.tsx
  17. 22
    4
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx
  18. 0
    75
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js
  19. 73
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx
  20. 28
    0
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts
  21. 24
    27
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx
  22. 6
    9
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx
  23. 24
    25
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx
  24. 10
    11
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx
  25. 6
    3
      server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx
  26. 10
    15
      server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx
  27. 4
    2
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts
  28. 4
    4
      server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts
  29. 28
    52
      server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.tsx
  30. 20
    19
      server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.tsx
  31. 180
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
  32. 89
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx
  33. 26
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx
  34. 69
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx
  35. 26
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx
  36. 39
    40
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx
  37. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx
  38. 9
    7
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.tsx
  39. 80
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.tsx
  40. 53
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx
  41. 9
    11
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx
  42. 23
    24
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx
  43. 4
    6
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx
  44. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx
  45. 30
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx
  46. 48
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx
  47. 46
    4
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx
  48. 80
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelectOption-test.tsx
  49. 155
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  50. 11
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap
  51. 93
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap
  52. 28
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap
  53. 26
    5
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap
  54. 4
    8
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap
  55. 0
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.tsx.snap
  56. 0
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.tsx.snap
  57. 107
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap
  58. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap
  59. 0
    12
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap
  60. 1
    1
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap
  61. 0
    14
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap
  62. 23
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap
  63. 42
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap
  64. 0
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.tsx.snap
  65. 25
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelectOption-test.tsx.snap
  66. 0
    84
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js
  67. 80
    0
      server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx
  68. 0
    87
      server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js
  69. 82
    0
      server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx
  70. 44
    42
      server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
  71. 3
    2
      server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts
  72. 34
    0
      server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.tsx
  73. 0
    72
      server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js
  74. 70
    0
      server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx
  75. 33
    38
      server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx
  76. 2
    3
      server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts
  77. 10
    2
      server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx
  78. 3
    3
      server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.tsx
  79. 3
    3
      server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.tsx
  80. 0
    71
      server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js
  81. 61
    0
      server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx
  82. 9
    2
      server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx
  83. 9
    2
      server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx
  84. 9
    2
      server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx
  85. 27
    28
      server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx
  86. 10
    2
      server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx
  87. 19
    30
      server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.tsx
  88. 18
    23
      server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.tsx
  89. 23
    24
      server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx
  90. 0
    34
      server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js
  91. 9
    19
      server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.tsx
  92. 10
    2
      server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx
  93. 0
    77
      server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js
  94. 67
    0
      server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx
  95. 49
    50
      server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx
  96. 2
    3
      server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts
  97. 38
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx
  98. 39
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx
  99. 73
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx
  100. 0
    0
      server/sonar-web/src/main/js/apps/projects/filters/__tests__/FilterHeader-test.tsx

+ 1
- 1
server/sonar-web/src/main/js/app/components/search/SearchResult.js Datei anzeigen

@@ -21,7 +21,7 @@
import React from 'react';
import { Link } from 'react-router';
/*:: import type { Component } from './utils'; */
import FavoriteIcon from '../../../components/common/FavoriteIcon';
import FavoriteIcon from '../../../components/icons-components/FavoriteIcon';
import QualifierIcon from '../../../components/shared/QualifierIcon';
import ClockIcon from '../../../components/common/ClockIcon';
import Tooltip from '../../../components/controls/Tooltip';

+ 0
- 1
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/GraphsTooltipsContentIssues-test.js.snap Datei anzeigen

@@ -21,7 +21,6 @@ exports[`should render correctly 1`] = `
</span>
<Rating
className="spacer-left"
muted={false}
small={true}
value="5.0"
/>

+ 40
- 0
server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts Datei anzeigen

@@ -0,0 +1,40 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 utils from '../utils';

describe('localizeSorting', () => {
it('localizes default sorting', () => {
expect(utils.localizeSorting()).toBe('projects.sort.name');
});

it('localizes custom sorting', () => {
expect(utils.localizeSorting('size')).toBe('projects.sort.size');
});
});

describe('parseSorting', () => {
it('parses ascending', () => {
expect(utils.parseSorting('size')).toEqual({ sortDesc: false, sortValue: 'size' });
});

it('parses descending', () => {
expect(utils.parseSorting('-size')).toEqual({ sortDesc: true, sortValue: 'size' });
});
});

server/sonar-web/src/main/js/apps/projects/components/AllProjects.js → server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx Datei anzeigen

@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import PageHeaderContainer from './PageHeaderContainer';
import ProjectsListContainer from './ProjectsListContainer';
@@ -29,32 +29,27 @@ import { parseUrlQuery } from '../store/utils';
import { translate } from '../../../helpers/l10n';
import * as utils from '../utils';
import * as storage from '../../../helpers/storage';
/*:: import type { RawQuery } from '../../../helpers/query'; */
import { RawQuery } from '../../../helpers/query';
import '../styles.css';

/*::
type Props = {|
isFavorite: boolean,
location: { pathname: string, query: RawQuery },
fetchProjects: (query: string, isFavorite: boolean, organization?: {}) => Promise<*>,
organization?: { key: string },
router: {
push: ({ pathname: string, query?: {} }) => void,
replace: ({ pathname: string, query?: {} }) => void
},
currentUser?: { isLoggedIn: boolean }
|};
*/

/*::
type State = {
query: RawQuery
};
*/

export default class AllProjects extends React.PureComponent {
/*:: props: Props; */
state /*: State */ = { query: {} };
interface Props {
isFavorite: boolean;
location: { pathname: string; query: RawQuery };
fetchProjects: (query: RawQuery, isFavorite: boolean, organization?: {}) => Promise<any>;
organization?: { key: string };
currentUser?: { isLoggedIn: boolean };
}

interface State {
query: RawQuery;
}

export default class AllProjects extends React.PureComponent<Props, State> {
state: State = { query: {} };

static contextTypes = {
router: PropTypes.object.isRequired
};

componentDidMount() {
this.handleQueryChange(true);
@@ -62,7 +57,7 @@ export default class AllProjects extends React.PureComponent {
footer && footer.classList.add('page-footer-with-sidebar');
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.location.query !== this.props.location.query) {
this.handleQueryChange(false);
}
@@ -82,23 +77,29 @@ export default class AllProjects extends React.PureComponent {
isFiltered = () => Object.keys(this.state.query).some(key => this.state.query[key] != null);

getSavedOptions = () => {
const options = {};
const options: {
sort?: string;
view?: string;
visualization?: string;
} = {};
if (storage.getSort()) {
options.sort = storage.getSort();
options.sort = storage.getSort() || undefined;
}
if (storage.getView()) {
options.view = storage.getView();
options.view = storage.getView() || undefined;
}
if (storage.getVisualization()) {
options.visualization = storage.getVisualization();
options.visualization = storage.getVisualization() || undefined;
}
return options;
};

handlePerspectiveChange = (
{ view, visualization } /*: { view: string, visualization?: string } */
) => {
const query /*: { view: ?string, visualization: ?string, sort?: ?string } */ = {
handlePerspectiveChange = ({ view, visualization }: { view: string; visualization?: string }) => {
const query: {
view: string | undefined;
visualization: string | undefined;
sort?: string | undefined;
} = {
view: view === 'overall' ? undefined : view,
visualization
};
@@ -110,7 +111,7 @@ export default class AllProjects extends React.PureComponent {
query.sort = (sort.sortDesc ? '-' : '') + utils.SORTING_SWITCH[sort.sortValue];
}
}
this.props.router.push({ pathname: this.props.location.pathname, query });
this.context.router.push({ pathname: this.props.location.pathname, query });
} else {
this.updateLocationQuery(query);
}
@@ -120,28 +121,28 @@ export default class AllProjects extends React.PureComponent {
storage.saveVisualization(visualization);
};

handleSortChange = (sort /*: string */, desc /*: boolean */) => {
handleSortChange = (sort: string, desc: boolean) => {
const asString = (desc ? '-' : '') + sort;
this.updateLocationQuery({ sort: asString });
storage.saveSort(asString);
};

handleQueryChange(initialMount /*: boolean */) {
handleQueryChange(initialMount: boolean) {
const query = parseUrlQuery(this.props.location.query);
const savedOptions = this.getSavedOptions();
const savedOptionsSet = savedOptions.sort || savedOptions.view || savedOptions.visualization;

// if there is no filter, but there are saved preferences in the localStorage
if (initialMount && !this.isFiltered() && savedOptionsSet) {
this.props.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
this.context.router.replace({ pathname: this.props.location.pathname, query: savedOptions });
} else {
this.setState({ query });
this.props.fetchProjects(query, this.props.isFavorite, this.props.organization);
}
}

updateLocationQuery = (newQuery /*: { [string]: ?string } */) => {
this.props.router.push({
updateLocationQuery = (newQuery: RawQuery) => {
this.context.router.push({
pathname: this.props.location.pathname,
query: {
...this.props.location.query,

server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.js → server/sonar-web/src/main/js/apps/projects/components/AllProjectsContainer.ts Datei anzeigen

@@ -18,8 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import AllProjects from './AllProjects';
import { fetchProjects } from '../store/actions';

export default connect(null, { fetchProjects })(withRouter(AllProjects));
export default connect<null, any, any>(null, { fetchProjects })(AllProjects);

server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js → server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.tsx Datei anzeigen

@@ -17,38 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import * as PropTypes from 'prop-types';
import AllProjectsContainer from './AllProjectsContainer';
import { getCurrentUser } from '../../../store/rootReducer';
import { isFavoriteSet, isAllSet } from '../../../helpers/storage';
import { searchProjects } from '../../../api/components';
/*:: import type { RawQuery } from '../../../helpers/query'; */

/*::
type Props = {
currentUser: { isLoggedIn: boolean },
location: { query: {} },
router: {
replace: (location: { pathname?: string, query?: RawQuery }) => void
}
};
*/
interface Props {
currentUser: { isLoggedIn: boolean };
location: { query: { [x: string]: string } };
}

/*::
type State = {
shouldBeRedirected?: boolean,
shouldForceSorting?: string
};
*/
interface State {
shouldBeRedirected?: boolean;
shouldForceSorting?: string;
}

class DefaultPageSelector extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */
class DefaultPageSelector extends React.PureComponent<Props, State> {
static contextTypes = {
router: PropTypes.object.isRequired
};

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.state = {};
}
@@ -57,13 +49,13 @@ class DefaultPageSelector extends React.PureComponent {
this.defineIfShouldBeRedirected();
}

componentDidUpdate(prevProps /*: Props */) {
componentDidUpdate(prevProps: Props) {
if (prevProps.location !== this.props.location) {
this.defineIfShouldBeRedirected();
} else if (this.state.shouldBeRedirected === true) {
this.props.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
this.context.router.replace({ ...this.props.location, pathname: '/projects/favorite' });
} else if (this.state.shouldForceSorting != null) {
this.props.router.replace({
this.context.router.replace({
...this.props.location,
query: {
...this.props.location.query,
@@ -117,8 +109,10 @@ class DefaultPageSelector extends React.PureComponent {
}
}

const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
currentUser: getCurrentUser(state)
});

export default connect(mapStateToProps)(withRouter(DefaultPageSelector));
export default connect<any, any, any>(mapStateToProps)(DefaultPageSelector);

export const UnconnectedDefaultPageSelector = DefaultPageSelector;

server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.js → server/sonar-web/src/main/js/apps/projects/components/EmptyInstance.tsx Datei anzeigen

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';

export default function EmptyInstance() {

server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js → server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.tsx Datei anzeigen

@@ -17,26 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { IndexLink, Link } from 'react-router';
import { translate } from '../../../helpers/l10n';
import { saveAll, saveFavorite } from '../../../helpers/storage';
/*:: import type { RawQuery } from '../../../helpers/query'; */
import { RawQuery } from '../../../helpers/query';

/*::
type Props = {
user: {
isLoggedIn?: boolean
},
organization?: { key: string },
query: RawQuery
};
*/

export default class FavoriteFilter extends React.PureComponent {
/*:: props: Props; */
interface Props {
user: { isLoggedIn?: boolean };
organization?: { key: string };
query: RawQuery;
}

export default class FavoriteFilter extends React.PureComponent<Props> {
handleSaveFavorite = () => {
if (!this.props.organization) {
saveFavorite();

server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.js → server/sonar-web/src/main/js/apps/projects/components/FavoriteFilterContainer.tsx Datei anzeigen

@@ -21,8 +21,8 @@ import { connect } from 'react-redux';
import FavoriteFilter from './FavoriteFilter';
import { getCurrentUser } from '../../../store/rootReducer';

const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
user: getCurrentUser(state)
});

export default connect(mapStateToProps)(FavoriteFilter);
export default connect<any, any, any>(mapStateToProps)(FavoriteFilter);

server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.js → server/sonar-web/src/main/js/apps/projects/components/FavoriteProjectsContainer.ts Datei anzeigen

@@ -18,14 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import AllProjects from './AllProjects';
import { getCurrentUser } from '../../../store/rootReducer';
import { fetchProjects } from '../store/actions';

const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
isFavorite: true,
currentUser: getCurrentUser(state)
});

export default connect(mapStateToProps, { fetchProjects })(withRouter(AllProjects));
export default connect<any, any, any>(mapStateToProps, { fetchProjects })(AllProjects);

server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.js → server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx Datei anzeigen

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import { translate } from '../../../helpers/l10n';


server/sonar-web/src/main/js/apps/projects/components/PageHeader.js → server/sonar-web/src/main/js/apps/projects/components/PageHeader.tsx Datei anzeigen

@@ -17,42 +17,45 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import SearchFilterContainer from '../filters/SearchFilterContainer';
import Tooltip from '../../../components/controls/Tooltip';
import PerspectiveSelect from './PerspectiveSelect';
import ProjectsSortingSelect from './ProjectsSortingSelect';
import { translate } from '../../../helpers/l10n';
/*:: import type { RawQuery } from '../../../helpers/query'; */
import { RawQuery } from '../../../helpers/query';

/*::
type Props = {|
currentUser?: { isLoggedIn: boolean },
isFavorite?: boolean,
onPerspectiveChange: ({ view: string, visualization?: string }) => void,
organization?: { key: string },
projects: Array<*>,
projectsAppState: { loading: boolean, total?: number },
query: RawQuery,
onSortChange: (sort: string, desc: boolean) => void,
selectedSort: string,
view: string,
visualization?: string
|};
*/
interface Props {
currentUser?: { isLoggedIn: boolean };
isFavorite?: boolean;
onPerspectiveChange: (x: { view: string; visualization?: string }) => void;
organization?: { key: string };
projects: Array<any>;
projectsAppState: { loading: boolean; total?: number };
query: RawQuery;
onSortChange: (sort: string, desc: boolean) => void;
selectedSort: string;
view: string;
visualization?: string;
}

export default function PageHeader(props: Props) {
const { projectsAppState, projects, currentUser, view } = props;
const limitReached =
projects != null && projectsAppState.total != null && projects.length < projectsAppState.total;
const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date';

return (
<header className="page-header projects-topbar-items">
<PerspectiveSelect
className="projects-topbar-item js-projects-perspective-select"
onChange={props.onPerspectiveChange}
view={props.view}
visualization={props.visualization}
/>

export default function PageHeader(props /*: Props */) {
const renderSortingSelect = () => {
const { projectsAppState, projects, currentUser, view } = props;
const limitReached =
projects != null &&
projectsAppState.total != null &&
projects.length < projectsAppState.total;
const defaultOption = currentUser && currentUser.isLoggedIn ? 'name' : 'analysis_date';
if (view === 'visualizations' && !limitReached) {
return (
{view === 'visualizations' && !limitReached ? (
<Tooltip overlay={translate('projects.sort.disabled')}>
<div className="projects-topbar-item disabled">
<ProjectsSortingSelect
@@ -64,29 +67,15 @@ export default function PageHeader(props /*: Props */) {
/>
</div>
</Tooltip>
);
}
return (
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption={defaultOption}
onChange={props.onSortChange}
selectedSort={props.selectedSort}
view={props.view}
/>
);
};

return (
<header className="page-header projects-topbar-items">
<PerspectiveSelect
className="projects-topbar-item js-projects-perspective-select"
onChange={props.onPerspectiveChange}
view={props.view}
visualization={props.visualization}
/>

{renderSortingSelect()}
) : (
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption={defaultOption}
onChange={props.onSortChange}
selectedSort={props.selectedSort}
view={props.view}
/>
)}

<SearchFilterContainer
className="projects-topbar-item projects-topbar-item-search"

server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.js → server/sonar-web/src/main/js/apps/projects/components/PageHeaderContainer.ts Datei anzeigen

@@ -21,9 +21,9 @@ import { connect } from 'react-redux';
import PageHeader from './PageHeader';
import { getProjects, getProjectsAppState } from '../../../store/rootReducer';

const mapStateToProps = state => ({
const mapStateToProps = (state: any) => ({
projects: getProjects(state),
projectsAppState: getProjectsAppState(state)
});

export default connect(mapStateToProps)(PageHeader);
export default connect<any, any, any>(mapStateToProps)(PageHeader);

server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js → server/sonar-web/src/main/js/apps/projects/components/PageSidebar.tsx Datei anzeigen

@@ -17,8 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import { Link } from 'react-router';
import FavoriteFilterContainer from './FavoriteFilterContainer';
import LanguagesFilterContainer from '../filters/LanguagesFilterContainer';
@@ -37,21 +36,18 @@ import SecurityFilter from '../filters/SecurityFilter';
import SizeFilter from '../filters/SizeFilter';
import TagsFilterContainer from '../filters/TagsFilterContainer';
import { translate } from '../../../helpers/l10n';
/*:: import type { RawQuery } from '../../../helpers/query'; */
import { RawQuery } from '../../../helpers/query';

/*::
type Props = {
isFavorite: boolean,
organization?: { key: string },
query: RawQuery,
view: string,
visualization: string
};
*/
interface Props {
isFavorite: boolean;
organization?: { key: string };
query: RawQuery;
view: string;
visualization: string;
}

export default function PageSidebar(
{ query, isFavorite, organization, view, visualization } /*: Props */
) {
export default function PageSidebar(props: Props) {
const { query, isFavorite, organization, view, visualization } = props;
const isFiltered = Object.keys(query)
.filter(key => !['view', 'visualization', 'sort'].includes(key))
.some(key => query[key] != null);
@@ -60,12 +56,11 @@ export default function PageSidebar(
const pathname = basePathName + (isFavorite ? '/favorite' : '');
const facetProps = { query, isFavorite, organization };

let linkQuery;
let linkQuery: RawQuery | undefined = undefined;
if (view !== 'overall') {
linkQuery = { view };

if (view === 'visualizations') {
// $FlowFixMe
linkQuery.visualization = visualization;
}
}

server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.js → server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelect.tsx Datei anzeigen

@@ -17,33 +17,32 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import Select from 'react-select';
import PerspectiveSelectOption from './PerspectiveSelectOption';
import * as React from 'react';
import * as Select from 'react-select';
import PerspectiveSelectOption, { Option } from './PerspectiveSelectOption';
import { translate } from '../../../helpers/l10n';
import { VIEWS, VISUALIZATIONS } from '../utils';

/*::
export type Option = { label: string, type: string, value: string };
*/

/*::
type Props = {|
className?: string,
onChange: ({ view: string, visualization?: string }) => void,
view: string,
visualization?: string
|};
*/
interface Props {
className?: string;
onChange: (x: { view: string; visualization?: string }) => void;
view: string;
visualization?: string;
}

export default class PerspectiveSelect extends React.PureComponent {
/*:: options: Array<Option>; */
/*:: props: Props; */
export default class PerspectiveSelect extends React.PureComponent<Props> {
handleChange = (option: Option) => {
if (option.type === 'view') {
this.props.onChange({ view: option.value });
} else if (option.type === 'visualization') {
this.props.onChange({ view: 'visualizations', visualization: option.value });
}
};

constructor(props /*: Props */) {
super(props);
this.options = [
render() {
const { view, visualization } = this.props;
const perspective = view === 'visualizations' ? visualization : view;
const options = [
...VIEWS.map(opt => ({
type: 'view',
value: opt,
@@ -55,19 +54,6 @@ export default class PerspectiveSelect extends React.PureComponent {
label: translate('projects.visualization', opt)
}))
];
}

handleChange = (option /*: Option */) => {
if (option.type === 'view') {
this.props.onChange({ view: option.value });
} else if (option.type === 'visualization') {
this.props.onChange({ view: 'visualizations', visualization: option.value });
}
};

render() {
const { view, visualization } = this.props;
const perspective = view === 'visualizations' ? visualization : view;
return (
<div className={this.props.className}>
<label>{translate('projects.perspective')}:</label>
@@ -76,7 +62,7 @@ export default class PerspectiveSelect extends React.PureComponent {
clearable={false}
onChange={this.handleChange}
optionComponent={PerspectiveSelectOption}
options={this.options}
options={options}
searchable={false}
value={perspective}
/>

server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.js → server/sonar-web/src/main/js/apps/projects/components/PerspectiveSelectOption.tsx Datei anzeigen

@@ -17,37 +17,37 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import BubblesIcon from '../../../components/icons-components/BubblesIcon';
import ListIcon from '../../../components/icons-components/ListIcon';
/*:: import type { Option } from './PerspectiveSelect'; */

/*::
type Props = {
option: Option,
children?: Element | Text,
className?: string,
isFocused?: boolean,
onFocus: (Option, MouseEvent) => void,
onSelect: (Option, MouseEvent) => void
};
*/
export interface Option {
label: string;
type: string;
value: string;
}

export default class PerspectiveSelectOption extends React.PureComponent {
/*:: props: Props; */
interface Props {
option: Option;
children?: React.ReactNode;
className?: string;
isFocused?: boolean;
onFocus: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void;
onSelect: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void;
}

handleMouseDown = (event /*: MouseEvent */) => {
export default class PerspectiveSelectOption extends React.PureComponent<Props> {
handleMouseDown = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
this.props.onSelect(this.props.option, event);
};

handleMouseEnter = (event /*: MouseEvent */) => {
handleMouseEnter = (event: React.SyntheticEvent<HTMLElement>) => {
this.props.onFocus(this.props.option, event);
};

handleMouseMove = (event /*: MouseEvent */) => {
handleMouseMove = (event: React.SyntheticEvent<HTMLElement>) => {
if (this.props.isFocused) {
return;
}

server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardContainer.tsx Datei anzeigen

@@ -17,20 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { connect } from 'react-redux';
import ProjectCardLeak from './ProjectCardLeak';
import ProjectCardOverall from './ProjectCardOverall';
import { getComponent, getComponentMeasures } from '../../../store/rootReducer';

function ProjectCard(props /*: { type?: string } */) {
interface Props {
measures?: { [key: string]: string };
organization?: { key: string };
project?: {
analysisDate?: string;
key: string;
leakPeriodDate?: string;
name: string;
tags: Array<string>;
isFavorite?: boolean;
organization?: string;
visibility?: string;
};
type?: string;
}

function ProjectCard(props: Props) {
if (props.type === 'leak') {
return <ProjectCardLeak {...props} />;
}
return <ProjectCardOverall {...props} />;
}

export default connect((state, ownProps) => ({
const mapStateToProps = (state: any, ownProps: any) => ({
project: getComponent(state, ownProps.projectKey),
measures: getComponentMeasures(state, ownProps.projectKey)
}))(ProjectCard);
});

export default connect(mapStateToProps)(ProjectCard);

+ 0
- 75
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.js Datei anzeigen

@@ -1,75 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import { sortBy } from 'lodash';
import { connect } from 'react-redux';
import Tooltip from '../../../components/controls/Tooltip';
import { getLanguages } from '../../../store/rootReducer';
import { translate } from '../../../helpers/l10n';

class ProjectCardLanguages extends React.PureComponent {
getLanguageName(key) {
if (key === '<null>') {
return translate('unknown');
}
const language = this.props.languages[key];
return language != null ? language.name : key;
}

render() {
const { distribution } = this.props;

if (distribution == null) {
return null;
}
const parsedLanguages = distribution.split(';').map(item => item.split('='));
const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l =>
this.getLanguageName(l[0])
);

const tooltip = (
<span>
{finalLanguages.map(language => (
<span key={language}>
{language}
<br />
</span>
))}
</span>
);

const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');

return (
<div className="project-card-languages">
<Tooltip placement="bottom" overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
</div>
);
}
}

const mapStateToProps = state => ({
languages: getLanguages(state)
});

export default connect(mapStateToProps)(ProjectCardLanguages);

+ 73
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguages.tsx Datei anzeigen

@@ -0,0 +1,73 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 { sortBy } from 'lodash';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';

interface Languages {
[key: string]: { key: string; name: string };
}

interface Props {
distribution?: string;
languages: Languages;
}

export default function ProjectCardLanguages({ distribution, languages }: Props) {
if (distribution == undefined) {
return null;
}

const parsedLanguages = distribution.split(';').map(item => item.split('='));
const finalLanguages = sortBy(parsedLanguages, l => -1 * Number(l[1])).map(l =>
getLanguageName(languages, l[0])
);

const tooltip = (
<span>
{finalLanguages.map(language => (
<span key={language}>
{language}
<br />
</span>
))}
</span>
);

const languagesText =
finalLanguages.slice(0, 2).join(', ') + (finalLanguages.length > 2 ? ', ...' : '');

return (
<div className="project-card-languages">
<Tooltip placement="bottom" overlay={tooltip}>
<span>{languagesText}</span>
</Tooltip>
</div>
);
}

function getLanguageName(languages: Languages, key: string): string {
if (key === '<null>') {
return translate('unknown');
}
const language = languages[key];
return language != null ? language.name : key;
}

+ 28
- 0
server/sonar-web/src/main/js/apps/projects/components/ProjectCardLanguagesContainer.ts Datei anzeigen

@@ -0,0 +1,28 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 { connect } from 'react-redux';
import ProjectCardLanguages from './ProjectCardLanguages';
import { getLanguages } from '../../../store/rootReducer';

const mapStateToProps = (state: any) => ({
languages: getLanguages(state)
});

export default connect<any, any, any>(mapStateToProps)(ProjectCardLanguages);

server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeak.tsx Datei anzeigen

@@ -17,12 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import { Link } from 'react-router';
import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter.tsx';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import ProjectCardQualityGate from './ProjectCardQualityGate';
import ProjectCardLeakMeasures from './ProjectCardLeakMeasures';
import FavoriteContainer from '../../../components/controls/FavoriteContainer';
@@ -31,37 +30,35 @@ import TagsList from '../../../components/tags/TagsList';
import PrivateBadge from '../../../components/common/PrivateBadge';
import { translate, translateWithParameters } from '../../../helpers/l10n';

/*::
type Props = {
measures: { [string]: string },
organization?: { key: string },
interface Props {
measures?: { [key: string]: string };
organization?: { key: string };
project?: {
analysisDate?: string,
key: string,
leakPeriodDate?: string,
name: string,
tags: Array<string>,
isFavorite?: boolean,
organization?: string,
visibility?: boolean
}
};
*/
analysisDate?: string;
key: string;
leakPeriodDate?: string;
name: string;
tags: Array<string>;
isFavorite?: boolean;
organization?: string;
visibility?: string;
};
}

export default function ProjectCardLeak({ measures, organization, project } /*: Props */) {
if (project == null) {
export default function ProjectCardLeak({ measures, organization, project }: Props) {
if (project == undefined) {
return null;
}

const isProjectAnalyzed = project.analysisDate != null;
const isPrivate = project.visibility === 'private';
const hasLeakPeriodStart = project.leakPeriodDate != null;
const hasLeakPeriodStart = project.leakPeriodDate != undefined;
const hasTags = project.tags.length > 0;
const showOrganization = organization == null && project.organization != null;
const showOrganization = organization == undefined && project.organization != undefined;

// check for particular measures because only some measures can be loaded
// if coming from visualizations tab
const areProjectMeasuresLoaded = measures != null && measures['new_bugs'];
const areProjectMeasuresLoaded = measures != undefined && measures['new_bugs'];

const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
const className = classNames('boxed-group', 'project-card', {
@@ -82,7 +79,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*:
)}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{displayQualityGate && <ProjectCardQualityGate status={measures['alert_status']} />}
{displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />}
<div className="pull-right text-right">
{isPrivate && <PrivateBadge className="spacer-left" tooltipPlacement="left" />}
{hasTags && <TagsList tags={project.tags} customClass="spacer-left" />}
@@ -91,7 +88,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*:
hasLeakPeriodStart && (
<div className="project-card-dates note text-right pull-right">
{hasLeakPeriodStart && (
<DateFromNow date={project.leakPeriodDate}>
<DateFromNow date={project.leakPeriodDate!}>
{fromNow => (
<span className="project-card-leak-date pull-right">
{translateWithParameters('projects.leak_period_x', fromNow)}
@@ -100,7 +97,7 @@ export default function ProjectCardLeak({ measures, organization, project } /*:
</DateFromNow>
)}
{isProjectAnalyzed && (
<DateTimeFormatter date={project.analysisDate}>
<DateTimeFormatter date={project.analysisDate!}>
{formattedDate => (
<span>
{translateWithParameters('projects.last_analysis_on_x', formattedDate)}

server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardLeakMeasures.tsx Datei anzeigen

@@ -17,8 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import Measure from '../../../components/measure/Measure';
import BugIcon from '../../../components/icons-components/BugIcon';
import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
@@ -26,14 +25,12 @@ import Rating from '../../../components/ui/Rating';
import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
measures?: { [string]: string }
};
*/
interface Props {
measures?: { [key: string]: string };
}

export default function ProjectCardLeakMeasures({ measures } /*: Props */) {
if (measures == null) {
export default function ProjectCardLeakMeasures({ measures }: Props) {
if (measures == undefined) {
return null;
}


server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverall.tsx Datei anzeigen

@@ -17,9 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import { Link } from 'react-router';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import ProjectCardQualityGate from './ProjectCardQualityGate';
@@ -30,36 +29,36 @@ import TagsList from '../../../components/tags/TagsList';
import PrivateBadge from '../../../components/common/PrivateBadge';
import { translate, translateWithParameters } from '../../../helpers/l10n';

/*::
type Props = {
measures: { [string]: string },
organization?: { key: string },
interface Props {
measures?: { [key: string]: string };
organization?: { key: string };
project?: {
analysisDate?: string,
key: string,
name: string,
tags: Array<string>,
isFavorite?: boolean,
organization?: string,
visibility?: boolean
}
};
*/
analysisDate?: string;
key: string;
name: string;
tags: Array<string>;
isFavorite?: boolean;
organization?: string;
visibility?: string;
};
}

export default function ProjectCardOverall({ measures, organization, project } /*: Props */) {
if (project == null) {
export default function ProjectCardOverall({ measures, organization, project }: Props) {
if (project == undefined) {
return null;
}

const isProjectAnalyzed = project.analysisDate != null;
const isProjectAnalyzed = project.analysisDate != undefined;
const isPrivate = project.visibility === 'private';
const hasTags = project.tags.length > 0;
const showOrganization = organization == null && project.organization != null;
const showOrganization = organization == undefined && project.organization != undefined;

// check for particular measures because only some measures can be loaded
// if coming from visualizations tab
const areProjectMeasuresLoaded =
measures != null && measures['reliability_rating'] != null && measures['sqale_rating'] != null;
measures != undefined &&
measures['reliability_rating'] != undefined &&
measures['sqale_rating'] != undefined;

const displayQualityGate = areProjectMeasuresLoaded && isProjectAnalyzed;
const className = classNames('boxed-group', 'project-card', {
@@ -69,7 +68,7 @@ export default function ProjectCardOverall({ measures, organization, project } /
return (
<div data-key={project.key} className={className}>
<div className="boxed-group-header clearfix">
{project.isFavorite != null && (
{project.isFavorite != undefined && (
<FavoriteContainer className="spacer-right" componentKey={project.key} />
)}
<h2 className="project-card-name">
@@ -80,12 +79,12 @@ export default function ProjectCardOverall({ measures, organization, project } /
)}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
{displayQualityGate && <ProjectCardQualityGate status={measures['alert_status']} />}
{displayQualityGate && <ProjectCardQualityGate status={measures!['alert_status']} />}
<div className="pull-right text-right">
{isPrivate && <PrivateBadge className="spacer-left" tooltipPlacement="left" />}
{hasTags && <TagsList tags={project.tags} customClass="spacer-left" />}
</div>
{isProjectAnalyzed && (
{project.analysisDate && (
<div className="project-card-dates note text-right">
<DateTimeFormatter date={project.analysisDate}>
{formattedDate => (

server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardOverallMeasures.tsx Datei anzeigen

@@ -17,9 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import ProjectCardLanguages from './ProjectCardLanguages';
import * as React from 'react';
import ProjectCardLanguagesContainer from './ProjectCardLanguagesContainer';
import Measure from '../../../components/measure/Measure';
import Rating from '../../../components/ui/Rating';
import CoverageRating from '../../../components/ui/CoverageRating';
@@ -27,14 +26,12 @@ import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
measures?: { [string]: string }
};
*/
interface Props {
measures?: { [key: string]: string };
}

export default function ProjectCardOverallMeasures({ measures } /*: Props */) {
if (measures == null) {
export default function ProjectCardOverallMeasures({ measures }: Props) {
if (measures == undefined) {
return null;
}

@@ -128,7 +125,9 @@ export default function ProjectCardOverallMeasures({ measures } /*: Props */) {
/>
</div>
<div className="project-card-measure-label">
<ProjectCardLanguages distribution={measures['ncloc_language_distribution']} />
<ProjectCardLanguagesContainer
distribution={measures['ncloc_language_distribution']}
/>
</div>
</div>
</div>

server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.js → server/sonar-web/src/main/js/apps/projects/components/ProjectCardQualityGate.tsx Datei anzeigen

@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import Level from '../../../components/ui/Level';
import Tooltip from '../../../components/controls/Tooltip';
import { formatMeasure } from '../../../helpers/measures';
import { translateWithParameters } from '../../../helpers/l10n';

export default function ProjectCardQualityGate({ status } /*: { status?: string } */) {
interface Props {
status?: string;
}

export default function ProjectCardQualityGate({ status }: Props) {
if (!status) {
return null;
}

server/sonar-web/src/main/js/apps/projects/components/ProjectsList.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsList.tsx Datei anzeigen

@@ -17,26 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import ProjectCardContainer from './ProjectCardContainer';
import NoFavoriteProjects from './NoFavoriteProjects';
import EmptyInstance from './EmptyInstance';
import EmptySearch from '../../../components/common/EmptySearch';

/*::
type Props = {
projects?: Array<string>,
isFavorite: boolean,
isFiltered: boolean,
organization?: { key: string },
cardType?: string
};
*/

export default class ProjectsList extends React.PureComponent {
/*:: props: Props; */
interface Props {
cardType?: string;
isFavorite: boolean;
isFiltered: boolean;
organization?: { key: string };
projects?: string[];
}

export default class ProjectsList extends React.PureComponent<Props> {
renderNoProjects() {
if (this.props.isFavorite && !this.props.isFiltered) {
return <NoFavoriteProjects />;
@@ -50,7 +45,7 @@ export default class ProjectsList extends React.PureComponent {
render() {
const { projects } = this.props;

if (projects == null) {
if (projects == undefined) {
return null;
}


server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsListContainer.ts Datei anzeigen

@@ -21,7 +21,9 @@ import { connect } from 'react-redux';
import ProjectsList from './ProjectsList';
import { getProjects, getProjectsAppState } from '../../../store/rootReducer';

export default connect(state => ({
const mapStateToProps = (state: any) => ({
projects: getProjects(state),
total: getProjectsAppState(state).total
}))(ProjectsList);
});

export default connect<any, any, any>(mapStateToProps)(ProjectsList);

server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsListFooterContainer.ts Datei anzeigen

@@ -20,9 +20,9 @@
import { connect } from 'react-redux';
import { getProjects, getProjectsAppState } from '../../../store/rootReducer';
import { fetchMoreProjects } from '../store/actions';
import ProjectsListFooter from './ProjectsListFooter';
import ListFooter from '../../../components/controls/ListFooter';

const mapStateToProps = state => {
const mapStateToProps = (state: any) => {
const projects = getProjects(state);
const appState = getProjectsAppState(state);
return {
@@ -32,9 +32,9 @@ const mapStateToProps = state => {
};
};

const mapDispatchToProps = (dispatch, ownProps) => ({
const mapDispatchToProps = (dispatch: any, ownProps: any) => ({
loadMore: () =>
dispatch(fetchMoreProjects(ownProps.query, ownProps.isFavorite, ownProps.organization))
});

export default connect(mapStateToProps, mapDispatchToProps)(ProjectsListFooter);
export default connect(mapStateToProps, mapDispatchToProps)(ListFooter);

server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelect.tsx Datei anzeigen

@@ -17,75 +17,51 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { sortBy } from 'lodash';
import Select from 'react-select';
import ProjectsSortingSelectOption from './ProjectsSortingSelectOption';
import * as Select from 'react-select';
import ProjectsSortingSelectOption, { Option } from './ProjectsSortingSelectOption';
import SortAscIcon from '../../../components/icons-components/SortAscIcon';
import SortDescIcon from '../../../components/icons-components/SortDescIcon';
import Tooltip from '../../../components/controls/Tooltip';
import { translate } from '../../../helpers/l10n';
import { SORTING_METRICS, SORTING_LEAK_METRICS, parseSorting } from '../utils';

/*::
export type Option = { label: string, value: string, class?: string, short?: string };
*/

/*::
type Props = {
className?: string,
onChange: (sort: string, desc: boolean) => void,
selectedSort: string,
view: string,
defaultOption: string
};
*/

/*::
type State = {
sortValue: string,
sortDesc: boolean
};
*/

export default class ProjectsSortingSelect extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */

constructor(props /*: Props */) {
super(props);
this.state = parseSorting(props.selectedSort);
}
interface Props {
className?: string;
defaultOption: string;
onChange: (sort: string, desc: boolean) => void;
selectedSort: string;
view: string;
}

componentDidUpdate(prevProps /*: Props */) {
if (prevProps.selectedSort !== this.props.selectedSort) {
this.setState(parseSorting(this.props.selectedSort));
}
}
export default class ProjectsSortingSelect extends React.PureComponent<Props> {
getSorting = () => parseSorting(this.props.selectedSort);

getOptions = () => {
const sortMetrics = this.props.view === 'leak' ? SORTING_LEAK_METRICS : SORTING_METRICS;
return sortBy(sortMetrics, opt => (opt.value === this.props.defaultOption ? 0 : 1)).map((
opt /*: { value: string, class?: string } */
) => ({
value: opt.value,
label: translate('projects.sorting', opt.value),
class: opt.class
return sortBy(
sortMetrics,
option => (option.value === this.props.defaultOption ? 0 : 1)
).map(option => ({
value: option.value,
label: translate('projects.sorting', option.value),
class: option.class
}));
};

handleDescToggle = (evt /*: Event & { currentTarget: HTMLElement } */) => {
evt.preventDefault();
evt.currentTarget.blur();
this.props.onChange(this.state.sortValue, !this.state.sortDesc);
handleDescToggle = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.currentTarget.blur();
const sorting = this.getSorting();
this.props.onChange(sorting.sortValue, !sorting.sortDesc);
};

handleSortChange = (option /*: Option */) =>
this.props.onChange(option.value, this.state.sortDesc);
handleSortChange = (option: Option) =>
this.props.onChange(option.value, this.getSorting().sortDesc);

render() {
const { sortDesc } = this.state;
const { sortDesc, sortValue } = this.getSorting();

return (
<div className={this.props.className}>
@@ -97,7 +73,7 @@ export default class ProjectsSortingSelect extends React.PureComponent {
optionComponent={ProjectsSortingSelectOption}
options={this.getOptions()}
searchable={false}
value={this.state.sortValue}
value={sortValue}
/>
<Tooltip
overlay={

server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.js → server/sonar-web/src/main/js/apps/projects/components/ProjectsSortingSelectOption.tsx Datei anzeigen

@@ -17,36 +17,37 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import classNames from 'classnames';
/*:: import type { Option } from './ProjectsSortingSelect'; */
import * as React from 'react';
import * as classNames from 'classnames';

/*::
type Props = {
option: Option,
children?: Element | Text,
className?: string,
isFocused?: boolean,
onFocus: (Option, MouseEvent) => void,
onSelect: (Option, MouseEvent) => void
};
*/
export interface Option {
label: string;
value: string;
class?: string;
short?: string;
}

export default class ProjectsSortingSelectOption extends React.PureComponent {
/*:: props: Props; */
interface Props {
option: Option;
children?: React.ReactNode;
className?: string;
isFocused?: boolean;
onFocus: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void;
onSelect: (option: Option, event: React.SyntheticEvent<HTMLElement>) => void;
}

handleMouseDown = (event /*: MouseEvent */) => {
export default class ProjectsSortingSelectOption extends React.PureComponent<Props> {
handleMouseDown = (event: React.SyntheticEvent<HTMLElement>) => {
event.preventDefault();
event.stopPropagation();
this.props.onSelect(this.props.option, event);
};

handleMouseEnter = (event /*: MouseEvent */) => {
handleMouseEnter = (event: React.SyntheticEvent<HTMLElement>) => {
this.props.onFocus(this.props.option, event);
};

handleMouseMove = (event /*: MouseEvent */) => {
handleMouseMove = (event: React.SyntheticEvent<HTMLElement>) => {
if (this.props.isFocused) {
return;
}

+ 180
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx Datei anzeigen

@@ -0,0 +1,180 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
jest.mock('../ProjectsListContainer', () => ({
default: function ProjectsListContainer() {
return null;
}
}));

jest.mock('../ProjectsListFooterContainer', () => ({
default: function ProjectsListFooterContainer() {
return null;
}
}));
jest.mock('../PageHeaderContainer', () => ({
default: function PageHeaderContainer() {
return null;
}
}));

jest.mock('../PageSidebar', () => ({
default: function PageSidebar() {
return null;
}
}));

jest.mock('../../../../helpers/storage', () => ({
getSort: () => null,
getView: jest.fn(() => null),
getVisualization: () => null,
saveSort: jest.fn(),
saveView: jest.fn(),
saveVisualization: jest.fn()
}));

import * as React from 'react';
import { mount, shallow } from 'enzyme';
import AllProjects from '../AllProjects';
import { getView, saveSort, saveView, saveVisualization } from '../../../../helpers/storage';

beforeEach(() => {
(getView as jest.Mock<any>).mockImplementation(() => null);
(saveSort as jest.Mock<any>).mockClear();
(saveView as jest.Mock<any>).mockClear();
(saveVisualization as jest.Mock<any>).mockClear();
});

it('renders', () => {
const wrapper = shallow(
<AllProjects
fetchProjects={jest.fn()}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
/>,
{ context: { router: {} } }
);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ query: { view: 'visualizations' } });
expect(wrapper).toMatchSnapshot();
});

it('fetches projects', () => {
const fetchProjects = jest.fn();
mountRender({ fetchProjects });
expect(fetchProjects).lastCalledWith(
{
coverage: null,
duplications: null,
gate: null,
languages: null,
maintainability: null,
new_coverage: null,
new_duplications: null,
new_lines: null,
new_maintainability: null,
new_reliability: null,
new_security: null,
reliability: null,
search: null,
security: null,
size: null,
sort: null,
tags: null,
view: undefined,
visualization: null
},
false,
undefined
);
});

it('redirects to the saved search', () => {
(getView as jest.Mock<any>).mockImplementation(() => 'leak');
const replace = jest.fn();
mountRender({}, jest.fn(), replace);
expect(replace).lastCalledWith({ pathname: '/projects', query: { view: 'leak' } });
});

it('changes sort', () => {
const push = jest.fn();
const wrapper = mountRender({}, push);
wrapper.find('PageHeaderContainer').prop<Function>('onSortChange')('size', false);
expect(push).lastCalledWith({ pathname: '/projects', query: { sort: 'size' } });
expect(saveSort).lastCalledWith('size');
});

it('changes perspective to leak', () => {
const push = jest.fn();
const wrapper = mountRender({}, push);
wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({ view: 'leak' });
expect(push).lastCalledWith({
pathname: '/projects',
query: { view: 'leak', visualization: undefined }
});
expect(saveSort).lastCalledWith(undefined);
expect(saveView).lastCalledWith('leak');
expect(saveVisualization).lastCalledWith(undefined);
});

it('updates sorting when changing perspective from leak', () => {
const push = jest.fn();
const wrapper = mountRender(
{ location: { pathname: '/projects', query: { sort: 'new_coverage', view: 'leak' } } },
push
);
wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({
view: undefined
});
expect(push).lastCalledWith({
pathname: '/projects',
query: { sort: 'coverage', view: undefined, visualization: undefined }
});
expect(saveSort).lastCalledWith('coverage');
expect(saveView).lastCalledWith(undefined);
expect(saveVisualization).lastCalledWith(undefined);
});

it('changes perspective to risk visualization', () => {
const push = jest.fn();
const wrapper = mountRender({}, push);
wrapper.find('PageHeaderContainer').prop<Function>('onPerspectiveChange')({
view: 'visualizations',
visualization: 'risk'
});
expect(push).lastCalledWith({
pathname: '/projects',
query: { view: 'visualizations', visualization: 'risk' }
});
expect(saveSort).lastCalledWith(undefined);
expect(saveView).lastCalledWith('visualizations');
expect(saveVisualization).lastCalledWith('risk');
});

function mountRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) {
return mount(
<AllProjects
fetchProjects={jest.fn()}
isFavorite={false}
location={{ pathname: '/projects', query: {} }}
{...props}
/>,
{ context: { router: { push, replace } } }
);
}

+ 89
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/DefaultPageSelector-test.tsx Datei anzeigen

@@ -0,0 +1,89 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
jest.mock('../AllProjectsContainer', () => ({
default: function AllProjectsContainer() {
return null;
}
}));

jest.mock('../../../../helpers/storage', () => ({
isFavoriteSet: jest.fn(),
isAllSet: jest.fn()
}));

jest.mock('../../../../api/components', () => ({
searchProjects: jest.fn()
}));

import * as React from 'react';
import { mount } from 'enzyme';
import { UnconnectedDefaultPageSelector } from '../DefaultPageSelector';
import { doAsync } from '../../../../helpers/testUtils';

const isFavoriteSet = require('../../../../helpers/storage').isFavoriteSet as jest.Mock<any>;
const isAllSet = require('../../../../helpers/storage').isAllSet as jest.Mock<any>;
const searchProjects = require('../../../../api/components').searchProjects as jest.Mock<any>;

beforeEach(() => {
isFavoriteSet.mockImplementation(() => false).mockClear();
isAllSet.mockImplementation(() => false).mockClear();
});

it('shows all projects with existing filter', () => {
const replace = jest.fn();
mountRender(undefined, { size: '1' }, replace);
expect(replace).not.toBeCalled();
});

it('shows all projects sorted by analysis date for anonymous', () => {
const replace = jest.fn();
mountRender({ isLoggedIn: false }, undefined, replace);
expect(replace).lastCalledWith({ query: { sort: '-analysis_date' } });
});

it('shows favorite projects', () => {
isFavoriteSet.mockImplementation(() => true);
const replace = jest.fn();
mountRender(undefined, undefined, replace);
expect(replace).lastCalledWith({ pathname: '/projects/favorite', query: {} });
});

it('shows all projects', () => {
isAllSet.mockImplementation(() => true);
const replace = jest.fn();
mountRender(undefined, undefined, replace);
expect(replace).not.toBeCalled();
});

it('fetches favorites', () => {
searchProjects.mockImplementation(() => Promise.resolve({ paging: { total: 3 } }));
const replace = jest.fn();
mountRender(undefined, undefined, replace);
return doAsync().then(() => {
expect(searchProjects).toHaveBeenLastCalledWith({ filter: 'isFavorite', ps: 1 });
expect(replace).toBeCalledWith({ pathname: '/projects/favorite', query: {} });
});
});

function mountRender(user: any = { isLoggedIn: true }, query: any = {}, replace: any = jest.fn()) {
return mount(<UnconnectedDefaultPageSelector currentUser={user} location={{ query }} />, {
context: { router: { replace } }
});
}

+ 26
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/EmptyInstance-test.tsx Datei anzeigen

@@ -0,0 +1,26 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 EmptyInstance from '../EmptyInstance';

it('renders', () => {
expect(shallow(<EmptyInstance />)).toMatchSnapshot();
});

+ 69
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/FavoriteFilter-test.tsx Datei anzeigen

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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.
*/
jest.mock('../../../../helpers/storage', () => ({
saveAll: jest.fn(),
saveFavorite: jest.fn()
}));

import * as React from 'react';
import { shallow } from 'enzyme';
import FavoriteFilter from '../FavoriteFilter';
import { saveAll, saveFavorite } from '../../../../helpers/storage';
import { click } from '../../../../helpers/testUtils';

const user = { isLoggedIn: true };
const query = { size: 1 };

beforeEach(() => {
(saveAll as jest.Mock<any>).mockClear();
(saveFavorite as jest.Mock<any>).mockClear();
});

it('renders for logged in user', () => {
expect(shallow(<FavoriteFilter query={query} user={user} />)).toMatchSnapshot();
});

it('saves last selection', () => {
const wrapper = shallow(<FavoriteFilter query={query} user={user} />);
click(wrapper.find('#favorite-projects'));
expect(saveFavorite).toBeCalled();
click(wrapper.find('#all-projects'));
expect(saveAll).toBeCalled();
});

it('handles organization', () => {
expect(
shallow(<FavoriteFilter organization={{ key: 'org' }} query={query} user={user} />)
).toMatchSnapshot();
});

it('does not save last selection with organization', () => {
const wrapper = shallow(
<FavoriteFilter organization={{ key: 'org' }} query={query} user={user} />
);
click(wrapper.find('#favorite-projects'));
expect(saveFavorite).not.toBeCalled();
click(wrapper.find('#all-projects'));
expect(saveAll).not.toBeCalled();
});

it('does not render for anonymous', () => {
expect(shallow(<FavoriteFilter query={query} user={{ isLoggedIn: false }} />)).toMatchSnapshot();
});

+ 26
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/NoFavoriteProjects-test.tsx Datei anzeigen

@@ -0,0 +1,26 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 NoFavoriteProjects from '../NoFavoriteProjects';

it('renders', () => {
expect(shallow(<NoFavoriteProjects />)).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/PageHeader-test.tsx Datei anzeigen

@@ -17,31 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import PageHeader from '../PageHeader';

it('should render correctly', () => {
expect(
shallow(<PageHeader query={{ search: 'test' }} projectsAppState={{ total: 12 }} />)
).toMatchSnapshot();
expect(shallowRender()).toMatchSnapshot();
});

it('should render correctly while loading', () => {
expect(
shallow(
<PageHeader
query={{ search: '' }}
isFavorite={true}
projectsAppState={{ loading: true, total: 2 }}
/>
)
).toMatchSnapshot();
expect(shallowRender({ projectsAppState: { loading: true, total: 2 } })).toMatchSnapshot();
});

it('should not render projects total', () => {
expect(
shallow(<PageHeader projectsAppState={{}} query={{ search: '' }} />)
shallowRender({ projectsAppState: {} })
.find('#projects-total')
.exists()
).toBeFalsy();
@@ -49,38 +39,47 @@ it('should not render projects total', () => {

it('should render disabled sorting options for visualizations', () => {
expect(
shallow(
<PageHeader
open={true}
projectsAppState={{}}
view="visualizations"
visualization="coverage"
/>
)
shallowRender({
open: true,
projectsAppState: {},
view: 'visualizations',
visualization: 'coverage'
})
).toMatchSnapshot();
});

it('should render switch the default sorting option for anonymous users', () => {
expect(
shallow(
<PageHeader
currentUser={{ isLoggedIn: true }}
open={true}
projectsAppState={{}}
view="overall"
visualization="risk"
/>
).find('ProjectsSortingSelect')
shallowRender({
currentUser: { isLoggedIn: true },
open: true,
projectsAppState: {},
visualization: 'risk'
}).find('ProjectsSortingSelect')
).toMatchSnapshot();

expect(
shallow(
<PageHeader
currentUser={{ isLoggedIn: false }}
open={true}
projectsAppState={{}}
view="leak"
visualization="risk"
/>
).find('ProjectsSortingSelect')
shallowRender({
currentUser: { isLoggedIn: false },
open: true,
projectsAppState: {},
view: 'leak',
visualization: 'risk'
}).find('ProjectsSortingSelect')
).toMatchSnapshot();
});

function shallowRender(props?: any) {
return shallow(
<PageHeader
onPerspectiveChange={jest.fn()}
onSortChange={jest.fn()}
projects={[]}
projectsAppState={{ loading: false, total: 12 }}
query={{ search: 'test' }}
selectedSort="size"
view="overall"
{...props}
/>
);
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/PageSidebar-test.tsx Datei anzeigen

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import PageSidebar from '../PageSidebar';


server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelect-test.tsx Datei anzeigen

@@ -17,17 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import PerspectiveSelect from '../PerspectiveSelect';

it('should render correctly', () => {
expect(shallow(<PerspectiveSelect view="overall" />)).toMatchSnapshot();
expect(shallow(<PerspectiveSelect onChange={jest.fn()} view="overall" />)).toMatchSnapshot();
});

it('should render with coverage selected', () => {
expect(
shallow(<PerspectiveSelect view="visualizations" visualization="coverage" />)
shallow(
<PerspectiveSelect onChange={jest.fn()} view="visualizations" visualization="coverage" />
)
).toMatchSnapshot();
});

@@ -35,9 +37,9 @@ it('should handle perspective change correctly', () => {
const onChange = jest.fn();
const instance = shallow(
<PerspectiveSelect view="visualizations" visualization="coverage" onChange={onChange} />
).instance();
instance.handleChange({ value: 'overall', type: 'view' });
instance.handleChange({ value: 'leak', type: 'view' });
instance.handleChange({ value: 'coverage', type: 'visualization' });
).instance() as PerspectiveSelect;
instance.handleChange({ label: 'overall', value: 'overall', type: 'view' });
instance.handleChange({ label: 'leak', value: 'leak', type: 'view' });
instance.handleChange({ label: 'coverage', value: 'coverage', type: 'visualization' });
expect(onChange.mock.calls).toMatchSnapshot();
});

+ 80
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/PerspectiveSelectOption-test.tsx Datei anzeigen

@@ -0,0 +1,80 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 PerspectiveSelectOption from '../PerspectiveSelectOption';

it('should render correctly for a view', () => {
expect(
shallow(
<PerspectiveSelectOption
onFocus={jest.fn()}
onSelect={jest.fn()}
option={{ value: 'overall', type: 'view', label: 'Overall' }}>
Overall
</PerspectiveSelectOption>
)
).toMatchSnapshot();
});

it('should render correctly for a visualization', () => {
expect(
shallow(
<PerspectiveSelectOption
onFocus={jest.fn()}
onSelect={jest.fn()}
option={{ value: 'coverage', type: 'visualization', label: 'Coverage' }}>
Coverage
</PerspectiveSelectOption>
)
).toMatchSnapshot();
});

it('selects option', () => {
const onSelect = jest.fn();
const option = { value: 'coverage', type: 'visualization', label: 'Coverage' };
const wrapper = shallow(
<PerspectiveSelectOption onFocus={jest.fn()} onSelect={onSelect} option={option} />
);
const event = { stopPropagation() {}, preventDefault() {} };
wrapper.simulate('mousedown', event);
expect(onSelect).toBeCalledWith(option, event);
});

it('focuses option', () => {
const onFocus = jest.fn();
const option = { value: 'coverage', type: 'visualization', label: 'Coverage' };
const wrapper = shallow(
<PerspectiveSelectOption onFocus={onFocus} onSelect={jest.fn()} option={option} />
);
const event = { stopPropagation() {}, preventDefault() {} };

wrapper.simulate('mouseenter', event);
expect(onFocus).toBeCalledWith(option, event);

onFocus.mockClear();
wrapper.simulate('mousemove', event);
expect(onFocus).toBeCalledWith(option, event);

onFocus.mockClear();
wrapper.setProps({ isFocused: true });
wrapper.simulate('mousemove', event);
expect(onFocus).not.toBeCalled();
});

+ 53
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLanguages-test.tsx Datei anzeigen

@@ -0,0 +1,53 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProjectCardLanguages from '../ProjectCardLanguages';

const languages = {
java: { key: 'java', name: 'Java' },
js: { key: 'js', name: 'JavaScript' }
};

it('renders', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=137;js=15" languages={languages} />)
).toMatchSnapshot();
});

it('sorts languages', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=13;js=152" languages={languages} />)
).toMatchSnapshot();
});

it('handles unknown languages', () => {
expect(
shallow(<ProjectCardLanguages distribution="java=13;cpp=18" languages={languages} />)
).toMatchSnapshot();

expect(
shallow(<ProjectCardLanguages distribution="java=13;<null>=18" languages={languages} />)
).toMatchSnapshot();
});

it('does not render', () => {
expect(shallow(<ProjectCardLanguages languages={languages} />)).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeak-test.tsx Datei anzeigen

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardLeak from '../ProjectCardLeak';

@@ -32,11 +32,11 @@ const MEASURES = {
alert_status: 'OK',
reliability_rating: '1.0',
sqale_rating: '1.0',
new_bugs: 12
new_bugs: '12'
};

it('should display analysis date and leak start date', () => {
const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />);
const card = shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />);
expect(card.find('.project-card-dates').exists()).toBeTruthy();
expect(card.find('.project-card-dates').find('DateFromNow')).toHaveLength(1);
expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1);
@@ -44,14 +44,14 @@ it('should display analysis date and leak start date', () => {

it('should not display analysis date or leak start date', () => {
const project = { ...PROJECT, analysisDate: undefined };
const card = shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={project} />);
const card = shallow(<ProjectCardLeak measures={MEASURES} project={project} />);
expect(card.find('.project-card-dates').exists()).toBeFalsy();
});

it('should display loading', () => {
const measures = { ...MEASURES, new_bugs: undefined };
const measures = { alert_status: 'OK', reliability_rating: '1.0', sqale_rating: '1.0' };
expect(
shallow(<ProjectCardLeak type="leak" measures={measures} project={PROJECT} />)
shallow(<ProjectCardLeak measures={measures} project={PROJECT} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
@@ -60,7 +60,7 @@ it('should display loading', () => {
it('should display tags', () => {
const project = { ...PROJECT, tags: ['foo', 'bar'] };
expect(
shallow(<ProjectCardLeak type="leak" project={project} />)
shallow(<ProjectCardLeak project={project} />)
.find('TagsList')
.exists()
).toBeTruthy();
@@ -69,14 +69,12 @@ it('should display tags', () => {
it('should private badge', () => {
const project = { ...PROJECT, visibility: 'private' };
expect(
shallow(<ProjectCardLeak type="leak" project={project} />)
shallow(<ProjectCardLeak project={project} />)
.find('PrivateBadge')
.exists()
).toBeTruthy();
});

it('should display the leak measures and quality gate', () => {
expect(
shallow(<ProjectCardLeak type="leak" measures={MEASURES} project={PROJECT} />)
).toMatchSnapshot();
expect(shallow(<ProjectCardLeak measures={MEASURES} project={PROJECT} />)).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardLeakMeasures-test.tsx Datei anzeigen

@@ -17,38 +17,37 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardLeakMeasures from '../ProjectCardLeakMeasures';

const measures = {
alert_status: 'ERROR',
new_reliability_rating: '1.0',
new_bugs: '8',
new_security_rating: '2.0',
new_vulnerabilities: '2',
new_maintainability_rating: '1.0',
new_code_smells: '0',
new_coverage: '26.55',
new_duplicated_lines_density: '0.55',
new_lines: '87'
};

it('should render correctly with all data', () => {
const measures = {
alert_status: 'ERROR',
new_reliability_rating: '1.0',
new_bugs: '8',
new_security_rating: '2.0',
new_vulnerabilities: '2',
new_maintainability_rating: '1.0',
new_code_smells: '0',
new_coverage: '26.55',
new_duplicated_lines_density: '0.55',
new_lines: '87'
};
const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />);
expect(wrapper).toMatchSnapshot();
});

it('should render no data style new coverage, new duplications and new lines', () => {
const wrapper = shallow(
<ProjectCardLeakMeasures
measures={{
...measures,
new_coverage: undefined,
new_duplicated_lines_density: undefined,
new_lines: undefined
}}
/>
);
const measures = {
alert_status: 'ERROR',
new_reliability_rating: '1.0',
new_bugs: '8',
new_security_rating: '2.0',
new_vulnerabilities: '2',
new_maintainability_rating: '1.0',
new_code_smells: '0'
};
const wrapper = shallow(<ProjectCardLeakMeasures measures={measures} />);
expect(wrapper).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverall-test.tsx Datei anzeigen

@@ -17,13 +17,12 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardOverall from '../ProjectCardOverall';

const PROJECT = {
analysisDate: '2017-01-01',
leakPeriodDate: '2016-12-01',
key: 'foo',
name: 'Foo',
tags: []
@@ -32,7 +31,7 @@ const MEASURES = {
alert_status: 'OK',
reliability_rating: '1.0',
sqale_rating: '1.0',
new_bugs: 12
new_bugs: '12'
};

it('should display analysis date (and not leak period) when defined', () => {
@@ -49,14 +48,13 @@ it('should display analysis date (and not leak period) when defined', () => {
});

it('should display loading', () => {
const measures = { ...MEASURES, sqale_rating: undefined };
expect(
shallow(<ProjectCardOverall project={PROJECT} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
expect(
shallow(<ProjectCardOverall measures={measures} project={PROJECT} />)
shallow(<ProjectCardOverall measures={{ sqale_rating: '1.0' }} project={PROJECT} />)
.find('.boxed-group')
.hasClass('boxed-group-loading')
).toBeTruthy();
@@ -83,7 +81,7 @@ it('should display tags', () => {
it('should private badge', () => {
const project = { ...PROJECT, visibility: 'private' };
expect(
shallow(<ProjectCardOverall type="overall" project={project} />)
shallow(<ProjectCardOverall project={project} />)
.find('PrivateBadge')
.exists()
).toBeTruthy();

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardOverallMeasures-test.tsx Datei anzeigen

@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardOverallMeasures from '../ProjectCardOverallMeasures';


+ 30
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectCardQualityGate-test.tsx Datei anzeigen

@@ -0,0 +1,30 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProjectCardQualityGate from '../ProjectCardQualityGate';

it('renders', () => {
expect(shallow(<ProjectCardQualityGate status="ERROR" />)).toMatchSnapshot();
});

it('does not render', () => {
expect(shallow(<ProjectCardQualityGate />)).toMatchSnapshot();
});

+ 48
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsList-test.tsx Datei anzeigen

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 ProjectsList from '../ProjectsList';

it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('does not render without projects', () => {
expect(shallow(<ProjectsList isFavorite={false} isFiltered={false} />)).toMatchSnapshot();
});

it('renders different types of "no projects"', () => {
expect(shallowRender({ projects: [] })).toMatchSnapshot();
expect(shallowRender({ projects: [], isFiltered: true })).toMatchSnapshot();
expect(shallowRender({ projects: [], isFavorite: true })).toMatchSnapshot();
});

function shallowRender(props?: any) {
return shallow(
<ProjectsList
cardType="overall"
isFavorite={false}
isFiltered={false}
projects={['foo', 'bar']}
{...props}
/>
);
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.js → server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelect-test.tsx Datei anzeigen

@@ -17,13 +17,21 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectsSortingSelect from '../ProjectsSortingSelect';
import { click } from '../../../../helpers/testUtils';

it('should render correctly for overall view', () => {
expect(
shallow(<ProjectsSortingSelect selectedSort="name" view="overall" defaultOption="name" />)
shallow(
<ProjectsSortingSelect
defaultOption="name"
onChange={jest.fn()}
selectedSort="name"
view="overall"
/>
)
).toMatchSnapshot();
});

@@ -31,9 +39,10 @@ it('should render correctly for leak view', () => {
expect(
shallow(
<ProjectsSortingSelect
defaultOption="analysis_date"
onChange={jest.fn()}
selectedSort="new_coverage"
view="leak"
defaultOption="analysis_date"
/>
)
).toMatchSnapshot();
@@ -42,7 +51,40 @@ it('should render correctly for leak view', () => {
it('should handle the descending sort direction', () => {
expect(
shallow(
<ProjectsSortingSelect selectedSort="-vulnerability" view="overall" defaultOption="name" />
<ProjectsSortingSelect
defaultOption="name"
onChange={jest.fn()}
selectedSort="-vulnerability"
view="overall"
/>
)
).toMatchSnapshot();
});

it('changes sorting', () => {
const onChange = jest.fn();
const instance = shallow(
<ProjectsSortingSelect
defaultOption="name"
onChange={onChange}
selectedSort="-vulnerabilities"
view="overall"
/>
).instance() as ProjectsSortingSelect;
instance.handleSortChange({ label: 'size', value: 'size' });
expect(onChange).toBeCalledWith('size', true);
});

it('reverses sorting', () => {
const onChange = jest.fn();
const wrapper = shallow(
<ProjectsSortingSelect
defaultOption="name"
onChange={onChange}
selectedSort="-size"
view="overall"
/>
);
click(wrapper.find('a'));
expect(onChange).toBeCalledWith('size', false);
});

+ 80
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/ProjectsSortingSelectOption-test.tsx Datei anzeigen

@@ -0,0 +1,80 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 ProjectsSortingSelectOption from '../ProjectsSortingSelectOption';

it('renders', () => {
expect(
shallow(
<ProjectsSortingSelectOption
onFocus={jest.fn()}
onSelect={jest.fn()}
option={{ label: 'Size', value: 'size' }}>
Size
</ProjectsSortingSelectOption>
)
).toMatchSnapshot();
});

it('renders short', () => {
expect(
shallow(
<ProjectsSortingSelectOption
onFocus={jest.fn()}
onSelect={jest.fn()}
option={{ label: 'Size', short: 'Short', value: 'size' }}>
Size
</ProjectsSortingSelectOption>
)
).toMatchSnapshot();
});

it('selects option', () => {
const onSelect = jest.fn();
const option = { value: 'coverage', type: 'visualization', label: 'Coverage' };
const wrapper = shallow(
<ProjectsSortingSelectOption onFocus={jest.fn()} onSelect={onSelect} option={option} />
);
const event = { stopPropagation() {}, preventDefault() {} };
wrapper.simulate('mousedown', event);
expect(onSelect).toBeCalledWith(option, event);
});

it('focuses option', () => {
const onFocus = jest.fn();
const option = { value: 'coverage', type: 'visualization', label: 'Coverage' };
const wrapper = shallow(
<ProjectsSortingSelectOption onFocus={onFocus} onSelect={jest.fn()} option={option} />
);
const event = { stopPropagation() {}, preventDefault() {} };

wrapper.simulate('mouseenter', event);
expect(onFocus).toBeCalledWith(option, event);

onFocus.mockClear();
wrapper.simulate('mousemove', event);
expect(onFocus).toBeCalledWith(option, event);

onFocus.mockClear();
wrapper.setProps({ isFocused: true });
wrapper.simulate('mousemove', event);
expect(onFocus).not.toBeCalled();
});

+ 155
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap Datei anzeigen

@@ -0,0 +1,155 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<div
className="layout-page projects-page"
>
<HelmetWrapper
encodeSpecialCharacters={true}
title="projects.page"
/>
<div
className="layout-page-side-outer"
>
<div
className="layout-page-side projects-page-side"
style={
Object {
"top": 30,
}
}
>
<div
className="layout-page-side-inner"
>
<div
className="layout-page-filters"
>
<PageSidebar
isFavorite={false}
query={Object {}}
view="overall"
visualization="risk"
/>
</div>
</div>
</div>
</div>
<div
className="layout-page-main projects-page-content"
>
<div
className="layout-page-header-panel layout-page-main-header"
>
<div
className="layout-page-header-panel-inner layout-page-main-header-inner"
>
<div
className="layout-page-main-inner"
>
<PageHeaderContainer
isFavorite={false}
onPerspectiveChange={[Function]}
onSortChange={[Function]}
query={Object {}}
selectedSort="name"
view="overall"
visualization="risk"
/>
</div>
</div>
</div>
<div
className="layout-page-main-inner"
>
<ProjectsListContainer
cardType="overall"
isFavorite={false}
isFiltered={false}
/>
<ProjectsListFooterContainer
isFavorite={false}
query={Object {}}
/>
</div>
</div>
</div>
`;

exports[`renders 2`] = `
<div
className="layout-page projects-page"
>
<HelmetWrapper
encodeSpecialCharacters={true}
title="projects.page"
/>
<div
className="layout-page-side-outer"
>
<div
className="layout-page-side projects-page-side"
style={
Object {
"top": 30,
}
}
>
<div
className="layout-page-side-inner"
>
<div
className="layout-page-filters"
>
<PageSidebar
isFavorite={false}
query={
Object {
"view": "visualizations",
}
}
view="visualizations"
visualization="risk"
/>
</div>
</div>
</div>
</div>
<div
className="layout-page-main projects-page-content"
>
<div
className="layout-page-header-panel layout-page-main-header"
>
<div
className="layout-page-header-panel-inner layout-page-main-header-inner"
>
<div
className="layout-page-main-inner"
>
<PageHeaderContainer
isFavorite={false}
onPerspectiveChange={[Function]}
onSortChange={[Function]}
query={
Object {
"view": "visualizations",
}
}
selectedSort="name"
view="visualizations"
visualization="risk"
/>
</div>
</div>
</div>
<div
className="layout-page-main-inner"
>
<Connect(Visualizations)
visualization="risk"
/>
</div>
</div>
</div>
`;

+ 11
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/EmptyInstance-test.tsx.snap Datei anzeigen

@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<div
className="projects-empty-list"
>
<h3>
projects.no_projects.empty_instance
</h3>
</div>
`;

+ 93
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/FavoriteFilter-test.tsx.snap Datei anzeigen

@@ -0,0 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not render for anonymous 1`] = `null`;

exports[`handles organization 1`] = `
<header
className="page-header text-center"
>
<div
className="button-group"
>
<Link
activeClassName="button-active"
className="button"
id="favorite-projects"
onClick={[Function]}
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/organizations/org/projects/favorite",
"query": Object {
"size": 1,
},
}
}
>
my_favorites
</Link>
<IndexLink
activeClassName="button-active"
className="button"
id="all-projects"
onClick={[Function]}
to={
Object {
"pathname": "/organizations/org/projects",
"query": Object {
"size": 1,
},
}
}
>
all
</IndexLink>
</div>
</header>
`;

exports[`renders for logged in user 1`] = `
<header
className="page-header text-center"
>
<div
className="button-group"
>
<Link
activeClassName="button-active"
className="button"
id="favorite-projects"
onClick={[Function]}
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/projects/favorite",
"query": Object {
"size": 1,
},
}
}
>
my_favorites
</Link>
<IndexLink
activeClassName="button-active"
className="button"
id="all-projects"
onClick={[Function]}
to={
Object {
"pathname": "/projects",
"query": Object {
"size": 1,
},
}
}
>
all
</IndexLink>
</div>
</header>
`;

+ 28
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/NoFavoriteProjects-test.tsx.snap Datei anzeigen

@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<div
className="projects-empty-list"
>
<h3>
projects.no_favorite_projects
</h3>
<p
className="big-spacer-top"
>
projects.no_favorite_projects.engagement
</p>
<p
className="big-spacer-top"
>
<Link
className="button"
onlyActiveOnIndex={false}
style={Object {}}
to="/projects/all"
>
projects.explore_projects
</Link>
</p>
</div>
`;

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageHeader-test.tsx.snap Datei anzeigen

@@ -6,12 +6,17 @@ exports[`should render correctly 1`] = `
>
<PerspectiveSelect
className="projects-topbar-item js-projects-perspective-select"
onChange={[Function]}
view="overall"
/>
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption="analysis_date"
onChange={[Function]}
selectedSort="size"
view="overall"
/>
<withRouter(SearchFilterContainer)
<SearchFilterContainer
className="projects-topbar-item projects-topbar-item-search"
query={
Object {
@@ -41,17 +46,21 @@ exports[`should render correctly while loading 1`] = `
>
<PerspectiveSelect
className="projects-topbar-item js-projects-perspective-select"
onChange={[Function]}
view="overall"
/>
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption="analysis_date"
onChange={[Function]}
selectedSort="size"
view="overall"
/>
<withRouter(SearchFilterContainer)
<SearchFilterContainer
className="projects-topbar-item projects-topbar-item-search"
isFavorite={true}
query={
Object {
"search": "",
"search": "test",
}
}
/>
@@ -80,6 +89,7 @@ exports[`should render disabled sorting options for visualizations 1`] = `
>
<PerspectiveSelect
className="projects-topbar-item js-projects-perspective-select"
onChange={[Function]}
view="visualizations"
visualization="coverage"
/>
@@ -93,12 +103,19 @@ exports[`should render disabled sorting options for visualizations 1`] = `
<ProjectsSortingSelect
className="js-projects-sorting-select"
defaultOption="analysis_date"
onChange={[Function]}
selectedSort="size"
view="visualizations"
/>
</div>
</Tooltip>
<withRouter(SearchFilterContainer)
<SearchFilterContainer
className="projects-topbar-item projects-topbar-item-search"
query={
Object {
"search": "test",
}
}
/>
<div
className="projects-topbar-item is-last"
@@ -110,6 +127,8 @@ exports[`should render switch the default sorting option for anonymous users 1`]
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption="name"
onChange={[Function]}
selectedSort="size"
view="overall"
/>
`;
@@ -118,6 +137,8 @@ exports[`should render switch the default sorting option for anonymous users 2`]
<ProjectsSortingSelect
className="projects-topbar-item js-projects-sorting-select"
defaultOption="analysis_date"
onChange={[Function]}
selectedSort="size"
view="leak"
/>
`;

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PageSidebar-test.tsx.snap Datei anzeigen

@@ -91,14 +91,13 @@ exports[`should render \`leak\` view correctly 1`] = `
/>
<NewLinesFilter
isFavorite={false}
property="new_lines"
query={
Object {
"view": "leak",
}
}
/>
<Connect(withRouter(LanguagesFilter))
<Connect(LanguagesFilter)
isFavorite={false}
query={
Object {
@@ -106,7 +105,7 @@ exports[`should render \`leak\` view correctly 1`] = `
}
}
/>
<Connect(withRouter(TagsFilter))
<Connect(TagsFilter)
isFavorite={false}
query={
Object {
@@ -178,7 +177,6 @@ exports[`should render correctly 1`] = `
/>
<CoverageFilter
isFavorite={true}
property="coverage"
query={
Object {
"size": "3",
@@ -187,7 +185,6 @@ exports[`should render correctly 1`] = `
/>
<DuplicationsFilter
isFavorite={true}
property="duplications"
query={
Object {
"size": "3",
@@ -196,14 +193,13 @@ exports[`should render correctly 1`] = `
/>
<SizeFilter
isFavorite={true}
property="size"
query={
Object {
"size": "3",
}
}
/>
<Connect(withRouter(LanguagesFilter))
<Connect(LanguagesFilter)
isFavorite={true}
query={
Object {
@@ -211,7 +207,7 @@ exports[`should render correctly 1`] = `
}
}
/>
<Connect(withRouter(TagsFilter))
<Connect(TagsFilter)
isFavorite={true}
query={
Object {

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelect-test.tsx.snap Datei anzeigen


server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/PerspectiveSelectOption-test.tsx.snap Datei anzeigen


+ 107
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLanguages-test.tsx.snap Datei anzeigen

@@ -0,0 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not render 1`] = `null`;

exports[`handles unknown languages 1`] = `
<div
className="project-card-languages"
>
<Tooltip
overlay={
<span>
<span>
cpp
<br />
</span>
<span>
Java
<br />
</span>
</span>
}
placement="bottom"
>
<span>
cpp, Java
</span>
</Tooltip>
</div>
`;

exports[`handles unknown languages 2`] = `
<div
className="project-card-languages"
>
<Tooltip
overlay={
<span>
<span>
unknown
<br />
</span>
<span>
Java
<br />
</span>
</span>
}
placement="bottom"
>
<span>
unknown, Java
</span>
</Tooltip>
</div>
`;

exports[`renders 1`] = `
<div
className="project-card-languages"
>
<Tooltip
overlay={
<span>
<span>
Java
<br />
</span>
<span>
JavaScript
<br />
</span>
</span>
}
placement="bottom"
>
<span>
Java, JavaScript
</span>
</Tooltip>
</div>
`;

exports[`sorts languages 1`] = `
<div
className="project-card-languages"
>
<Tooltip
overlay={
<span>
<span>
JavaScript
<br />
</span>
<span>
Java
<br />
</span>
</span>
}
placement="bottom"
>
<span>
JavaScript, Java
</span>
</Tooltip>
</div>
`;

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeak-test.tsx.snap Datei anzeigen

@@ -50,7 +50,7 @@ exports[`should display the leak measures and quality gate 1`] = `
measures={
Object {
"alert_status": "OK",
"new_bugs": 12,
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardLeakMeasures-test.tsx.snap Datei anzeigen

@@ -28,8 +28,6 @@ exports[`should render correctly with all data 1`] = `
}
/>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -67,8 +65,6 @@ exports[`should render correctly with all data 1`] = `
}
/>
<Rating
muted={false}
small={false}
value="2.0"
/>
</div>
@@ -106,8 +102,6 @@ exports[`should render correctly with all data 1`] = `
}
/>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -242,8 +236,6 @@ exports[`should render no data style new coverage, new duplications and new line
}
/>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -281,8 +273,6 @@ exports[`should render no data style new coverage, new duplications and new line
}
/>
<Rating
muted={false}
small={false}
value="2.0"
/>
</div>
@@ -320,8 +310,6 @@ exports[`should render no data style new coverage, new duplications and new line
}
/>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverall-test.tsx.snap Datei anzeigen

@@ -47,7 +47,7 @@ exports[`should display the overall measures and quality gate 1`] = `
measures={
Object {
"alert_status": "OK",
"new_bugs": 12,
"new_bugs": "12",
"reliability_rating": "1.0",
"sqale_rating": "1.0",
}

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardOverallMeasures-test.tsx.snap Datei anzeigen

@@ -81,8 +81,6 @@ exports[`should render correctly with all data 1`] = `
className="project-card-measure-number"
>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -104,8 +102,6 @@ exports[`should render correctly with all data 1`] = `
className="project-card-measure-number"
>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -127,8 +123,6 @@ exports[`should render correctly with all data 1`] = `
className="project-card-measure-number"
>
<Rating
muted={false}
small={false}
value="1.0"
/>
</div>
@@ -153,8 +147,6 @@ exports[`should render correctly with all data 1`] = `
className="spacer-right"
>
<CoverageRating
muted={false}
size="normal"
value="88.3"
/>
</span>
@@ -192,8 +184,6 @@ exports[`should render correctly with all data 1`] = `
className="spacer-right"
>
<DuplicationsRating
muted={false}
small={false}
value={9.8}
/>
</span>
@@ -231,8 +221,6 @@ exports[`should render correctly with all data 1`] = `
className="spacer-right"
>
<SizeRating
muted={false}
small={false}
value={2053}
/>
</span>
@@ -274,8 +262,6 @@ exports[`should render ncloc correctly 1`] = `
className="spacer-right"
>
<SizeRating
muted={false}
small={false}
value={16549887}
/>
</span>

+ 23
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectCardQualityGate-test.tsx.snap Datei anzeigen

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not render 1`] = `null`;

exports[`renders 1`] = `
<div
className="project-card-measure project-card-quality-gate spacer-left"
>
<Tooltip
overlay="overview.quality_gate_x.ERROR"
placement="bottom"
>
<div
className="project-card-measure-inner"
>
<Level
level="ERROR"
small={true}
/>
</div>
</Tooltip>
</div>
`;

+ 42
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsList-test.tsx.snap Datei anzeigen

@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`does not render without projects 1`] = `null`;

exports[`renders 1`] = `
<div
className="projects-list"
>
<Connect(ProjectCard)
projectKey="foo"
type="overall"
/>
<Connect(ProjectCard)
projectKey="bar"
type="overall"
/>
</div>
`;

exports[`renders different types of "no projects" 1`] = `
<div
className="projects-list"
>
<EmptyInstance />
</div>
`;

exports[`renders different types of "no projects" 2`] = `
<div
className="projects-list"
>
<EmptySearch />
</div>
`;

exports[`renders different types of "no projects" 3`] = `
<div
className="projects-list"
>
<NoFavoriteProjects />
</div>
`;

server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.js.snap → server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelect-test.tsx.snap Datei anzeigen


+ 25
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/ProjectsSortingSelectOption-test.tsx.snap Datei anzeigen

@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders 1`] = `
<div
className=""
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseMove={[Function]}
title="Size"
>
Size
</div>
`;

exports[`renders short 1`] = `
<div
className=""
onMouseDown={[Function]}
onMouseEnter={[Function]}
onMouseMove={[Function]}
title="Size"
>
Short
</div>
`;

+ 0
- 84
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.js Datei anzeigen

@@ -1,84 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';

export default class CoverageFilter extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
query: PropTypes.object.isRequired,
isFavorite: PropTypes.bool,
organization: PropTypes.object,
property: PropTypes.string
};

static defaultProps = {
property: 'coverage'
};

getFacetValueForOption(facet, option) {
const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0', 'NO_DATA'];
return facet[map[option - 1]];
}

renderOption(option, selected) {
return (
<span>
{option < 6 && (
<CoverageRating
value={getCoverageRatingAverageValue(option)}
size="small"
muted={!selected}
/>
)}
<span className="spacer-left">
{option < 6 ? (
getCoverageRatingLabel(option)
) : (
<span className="big-spacer-left">{translate('no_data')}</span>
)}
</span>
</span>
);
}

render() {
return (
<FilterContainer
property={this.props.property}
className={this.props.className}
options={[1, 2, 3, 4, 5, 6]}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
highlightUnder={1}
highlightUnderMax={5}
header={<FilterHeader name={translate('metric_domain.Coverage')} />}
/>
);
}
}

+ 80
- 0
server/sonar-web/src/main/js/apps/projects/filters/CoverageFilter.tsx Datei anzeigen

@@ -0,0 +1,80 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import { Facet } from './Filter';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getCoverageRatingLabel, getCoverageRatingAverageValue } from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';

export interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
}

export default function CoverageFilter(props: Props) {
const { property = 'coverage' } = props;

return (
<FilterContainer
property={property}
className={props.className}
options={[1, 2, 3, 4, 5, 6]}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
highlightUnder={1}
highlightUnderMax={5}
header={<FilterHeader name={translate('metric_domain.Coverage')} />}
/>
);
}

function getFacetValueForOption(facet: Facet, option: number): number {
const map = ['80.0-*', '70.0-80.0', '50.0-70.0', '30.0-50.0', '*-30.0', 'NO_DATA'];
return facet[map[option - 1]];
}

function renderOption(option: number, selected: boolean) {
return (
<span>
{option < 6 && (
<CoverageRating
value={getCoverageRatingAverageValue(option)}
size="small"
muted={!selected}
/>
)}
<span className="spacer-left">
{option < 6 ? (
getCoverageRatingLabel(option)
) : (
<span className="big-spacer-left">{translate('no_data')}</span>
)}
</span>
</span>
);
}

+ 0
- 87
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.js Datei anzeigen

@@ -1,87 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import {
getDuplicationsRatingLabel,
getDuplicationsRatingAverageValue
} from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';

export default class DuplicationsFilter extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
query: PropTypes.object.isRequired,
isFavorite: PropTypes.bool,
organization: PropTypes.object,
property: PropTypes.string
};

static defaultProps = {
property: 'duplications'
};

getFacetValueForOption(facet, option) {
const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*', 'NO_DATA'];
return facet[map[option - 1]];
}

renderOption(option, selected) {
return (
<span>
{option < 6 && (
<DuplicationsRating
value={getDuplicationsRatingAverageValue(option)}
size="small"
muted={!selected}
/>
)}
<span className="spacer-left">
{option < 6 ? (
getDuplicationsRatingLabel(option)
) : (
<span className="big-spacer-left">{translate('no_data')}</span>
)}
</span>
</span>
);
}

render() {
return (
<FilterContainer
property={this.props.property}
className={this.props.className}
options={[1, 2, 3, 4, 5, 6]}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
highlightUnder={1}
highlightUnderMax={5}
header={<FilterHeader name={translate('metric_domain.Duplications')} />}
/>
);
}
}

+ 82
- 0
server/sonar-web/src/main/js/apps/projects/filters/DuplicationsFilter.tsx Datei anzeigen

@@ -0,0 +1,82 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import DuplicationsRating from '../../../components/ui/DuplicationsRating';
import {
getDuplicationsRatingLabel,
getDuplicationsRatingAverageValue
} from '../../../helpers/ratings';
import { translate } from '../../../helpers/l10n';
import { Facet } from './Filter';

export interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
}

export default function DuplicationsFilter(props: Props) {
const { property = 'duplications' } = props;
return (
<FilterContainer
property={property}
className={props.className}
options={[1, 2, 3, 4, 5, 6]}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
highlightUnder={1}
highlightUnderMax={5}
header={<FilterHeader name={translate('metric_domain.Duplications')} />}
/>
);
}

function getFacetValueForOption(facet: Facet, option: number) {
const map = ['*-3.0', '3.0-5.0', '5.0-10.0', '10.0-20.0', '20.0-*', 'NO_DATA'];
return facet[map[option - 1]];
}

function renderOption(option: number, selected: boolean) {
return (
<span>
{option < 6 && (
<DuplicationsRating
value={getDuplicationsRatingAverageValue(option)}
size="small"
muted={!selected}
/>
)}
<span className="spacer-left">
{option < 6 ? (
getDuplicationsRatingLabel(option)
) : (
<span className="big-spacer-left">{translate('no_data')}</span>
)}
</span>
</span>
);
}

server/sonar-web/src/main/js/apps/projects/filters/Filter.js → server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx Datei anzeigen

@@ -17,60 +17,58 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import * as React from 'react';
import * as classNames from 'classnames';
import { Link } from 'react-router';
import { getFilterUrl } from './utils';
import { formatMeasure } from '../../../helpers/measures';
import { translate } from '../../../helpers/l10n';

export default class Filter extends React.PureComponent {
static propTypes = {
property: PropTypes.string.isRequired,
className: PropTypes.string,
options: PropTypes.array.isRequired,
query: PropTypes.object.isRequired,
renderOption: PropTypes.func.isRequired,

value: PropTypes.any,
facet: PropTypes.object,
maxFacetValue: PropTypes.number,
optionClassName: PropTypes.string,
isFavorite: PropTypes.bool,
organization: PropTypes.object,

getFacetValueForOption: PropTypes.func,

halfWidth: PropTypes.bool,
highlightUnder: PropTypes.number,
highlightUnderMax: PropTypes.number,

header: PropTypes.object,
footer: PropTypes.object
};
export type Option = string | number;
export type Facet = { [x: string]: number };

static defaultProps = {
halfWidth: false
};
interface Props {
property: string;
className?: string;
options: Option[];
query: { [x: string]: any };
renderOption: (option: Option, isSelected: boolean) => React.ReactNode;

value?: Option | Option[];
facet?: Facet;
maxFacetValue?: number;
optionClassName?: string;
isFavorite?: boolean;
organization?: { key: string };

getFacetValueForOption?: (facet: Facet, option: Option) => void;

halfWidth?: boolean;
highlightUnder?: number;
highlightUnderMax?: number;

isSelected(option) {
header?: React.ReactNode;
footer?: React.ReactNode;
}

export default class Filter extends React.PureComponent<Props> {
isSelected(option: Option): boolean {
const { value } = this.props;
return Array.isArray(value) ? value.includes(option) : option === value;
}

highlightUnder(option) {
highlightUnder(option?: Option): boolean {
return (
this.props.highlightUnder != null &&
option !== null &&
option != null &&
option > this.props.highlightUnder &&
(this.props.highlightUnderMax == null || option < this.props.highlightUnderMax)
);
}

blurOnClick = (evt /*: Event & { currentTarget: HTMLElement } */) => evt.currentTarget.blur();
blurOnClick = (event: React.SyntheticEvent<HTMLElement>) => event.currentTarget.blur();

getPath(option) {
getPath(option: Option) {
const { property, value } = this.props;
let urlOption;

@@ -86,8 +84,8 @@ export default class Filter extends React.PureComponent {
return getFilterUrl(this.props, { [property]: urlOption });
}

renderOptionBar(facetValue) {
if (facetValue == null || !this.props.maxFacetValue) {
renderOptionBar(facetValue: number | undefined) {
if (facetValue == undefined || !this.props.maxFacetValue) {
return null;
}
return (
@@ -100,7 +98,7 @@ export default class Filter extends React.PureComponent {
);
}

renderOption(option) {
renderOption(option: Option) {
const { facet, getFacetValueForOption, value } = this.props;
const className = classNames(
'facet',
@@ -115,9 +113,13 @@ export default class Filter extends React.PureComponent {

const path = this.getPath(option);
const facetValue =
facet && getFacetValueForOption ? getFacetValueForOption(facet, option) : null;
facet && getFacetValueForOption ? getFacetValueForOption(facet, option) : undefined;

const isUnderSelectedOption = this.highlightUnder(value) && option > value;
const isUnderSelectedOption =
typeof value === 'number' &&
typeof option === 'number' &&
this.highlightUnder(value) &&
option > value;

return (
<Link
@@ -139,7 +141,7 @@ export default class Filter extends React.PureComponent {
);
}

renderOptions() {
renderOptions = () => {
const { options, highlightUnder } = this.props;
if (options && options.length > 0) {
if (highlightUnder != null) {
@@ -166,7 +168,7 @@ export default class Filter extends React.PureComponent {
} else {
return <div className="search-navigator-facet-empty">{translate('no_results')}</div>;
}
}
};

render() {
return (

server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.js → server/sonar-web/src/main/js/apps/projects/filters/FilterContainer.ts Datei anzeigen

@@ -24,9 +24,10 @@ import {
getProjectsAppMaxFacetValue
} from '../../../store/rootReducer';

const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state: any, ownProps: any) => ({
value: ownProps.query[ownProps.property],
facet: getProjectsAppFacetByProperty(state, ownProps.property),
maxFacetValue: getProjectsAppMaxFacetValue(state)
});
export default connect(mapStateToProps)(Filter);

export default connect<any, any, any>(mapStateToProps)(Filter);

+ 34
- 0
server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.tsx Datei anzeigen

@@ -0,0 +1,34 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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';

interface Props {
children?: React.ReactNode;
name: string;
}

export default function FilterHeader(props: Props) {
return (
<div className="search-navigator-facet-header projects-facet-header">
{props.name}
{props.children}
</div>
);
}

+ 0
- 72
server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.js Datei anzeigen

@@ -1,72 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import Rating from '../../../components/ui/Rating';
import { translate } from '../../../helpers/l10n';

export default class IssuesFilter extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
headerDetail: PropTypes.element,
isFavorite: PropTypes.bool,
organization: PropTypes.object,
name: PropTypes.string.isRequired,
property: PropTypes.string.isRequired,
query: PropTypes.object.isRequired
};

getFacetValueForOption(facet, option) {
return facet[option];
}

renderOption(option, selected) {
return (
<span>
<Rating value={option} small={true} muted={!selected} />
{option > 1 &&
option < 5 && <span className="note spacer-left">{translate('and_worse')}</span>}
</span>
);
}

render() {
return (
<FilterContainer
property={this.props.property}
className={this.props.className}
options={[1, 2, 3, 4, 5]}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
highlightUnder={1}
header={
<FilterHeader name={translate('metric_domain', this.props.name)}>
{this.props.headerDetail}
</FilterHeader>
}
/>
);
}
}

+ 70
- 0
server/sonar-web/src/main/js/apps/projects/filters/IssuesFilter.tsx Datei anzeigen

@@ -0,0 +1,70 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import Rating from '../../../components/ui/Rating';
import { translate } from '../../../helpers/l10n';
import { Facet } from './Filter';

interface Props {
className?: string;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
name: string;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
}

export default function IssuesFilter(props: Props) {
return (
<FilterContainer
property={props.property}
className={props.className}
options={[1, 2, 3, 4, 5]}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
highlightUnder={1}
header={
<FilterHeader name={translate('metric_domain', props.name)}>
{props.headerDetail}
</FilterHeader>
}
/>
);
}

function getFacetValueForOption(facet: Facet, option: number) {
return facet[option];
}

function renderOption(option: number, selected: boolean) {
return (
<span>
<Rating value={option} small={true} muted={!selected} />
{option > 1 &&
option < 5 && <span className="note spacer-left">{translate('and_worse')}</span>}
</span>
);
}

server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.js → server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilter.tsx Datei anzeigen

@@ -17,55 +17,49 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import { difference, sortBy } from 'lodash';
import Filter from './Filter';
import Filter, { Facet } from './Filter';
import FilterHeader from './FilterHeader';
import SearchableFilterFooter from './SearchableFilterFooter';
import SearchableFilterOption from './SearchableFilterOption';
import { getLanguageByKey } from '../../../store/languages/reducer';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
query: {},
languages: {},
router: { push: ({ pathname: string, query?: {} }) => void },
value?: Array<string>,
facet?: {},
isFavorite?: boolean,
organization?: {},
maxFacetValue?: number
};
*/
interface Languages {
[key: string]: { key: string; name: string };
}

const LIST_SIZE = 10;
interface Props {
facet?: Facet;
isFavorite?: boolean;
languages: Languages;
maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
value?: Array<string>;
}

export default class LanguagesFilter extends React.PureComponent {
/*:: props: Props; */
property = 'languages';
const LIST_SIZE = 10;

getSearchOptions(
facet /*: ?{} */,
languages /*: {} */
) /*: Array<{ label: string, value: string }> */ {
let languageKeys = Object.keys(languages);
if (facet) {
languageKeys = difference(languageKeys, Object.keys(facet));
export default class LanguagesFilter extends React.Component<Props> {
getSearchOptions = () => {
let languageKeys = Object.keys(this.props.languages);
if (this.props.facet) {
languageKeys = difference(languageKeys, Object.keys(this.props.facet));
}
return languageKeys
.slice(0, LIST_SIZE)
.map(key => ({ label: languages[key].name, value: key }));
}
.map(key => ({ label: this.props.languages[key].name, value: key }));
};

getSortedOptions(facet /*: {} */ = {}) {
return sortBy(Object.keys(facet), [option => -facet[option], option => option]);
}
getSortedOptions = (facet: Facet = {}) =>
sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]);

getFacetValueForOption = (facet /*: {} */ = {}, option /*: string */) => facet[option];
getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option];

renderOption = (option /*: string */) => (
renderOption = (option: string) => (
<SearchableFilterOption
optionKey={option}
option={getLanguageByKey(this.props.languages, option)}
@@ -73,9 +67,11 @@ export default class LanguagesFilter extends React.PureComponent {
);

render() {
const { property = 'languages' } = this.props;

return (
<Filter
property={this.property}
property={property}
options={this.getSortedOptions(this.props.facet)}
query={this.props.query}
renderOption={this.renderOption}
@@ -88,12 +84,11 @@ export default class LanguagesFilter extends React.PureComponent {
header={<FilterHeader name={translate('projects.facets.languages')} />}
footer={
<SearchableFilterFooter
property={this.property}
query={this.props.query}
options={this.getSearchOptions(this.props.facet, this.props.languages)}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
router={this.props.router}
options={this.getSearchOptions()}
property={property}
query={this.props.query}
/>
}
/>

server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.js → server/sonar-web/src/main/js/apps/projects/filters/LanguagesFilterContainer.ts Datei anzeigen

@@ -18,7 +18,6 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import LanguagesFilter from './LanguagesFilter';
import {
getProjectsAppFacetByProperty,
@@ -26,10 +25,10 @@ import {
getLanguages
} from '../../../store/rootReducer';

const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state: any, ownProps: any) => ({
languages: getLanguages(state),
value: ownProps.query['languages'],
facet: getProjectsAppFacetByProperty(state, 'languages'),
maxFacetValue: getProjectsAppMaxFacetValue(state)
});
export default connect(mapStateToProps)(withRouter(LanguagesFilter));
export default connect<any, any, any>(mapStateToProps)(LanguagesFilter);

server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/MaintainabilityFilter.tsx Datei anzeigen

@@ -17,9 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import IssuesFilter from './IssuesFilter';

export default function MaintainabilityFilter(props) {
interface Props {
className?: string;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function MaintainabilityFilter(props: Props) {
return <IssuesFilter {...props} name="Maintainability" property="maintainability" />;
}

server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewCoverageFilter.tsx Datei anzeigen

@@ -17,9 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import CoverageFilter from './CoverageFilter';
import * as React from 'react';
import CoverageFilter, { Props } from './CoverageFilter';

export default function NewCoverageFilter(props) {
export default function NewCoverageFilter(props: Props) {
return <CoverageFilter {...props} property="new_coverage" className="leak-facet-box" />;
}

server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewDuplicationsFilter.tsx Datei anzeigen

@@ -17,9 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import DuplicationsFilter from './DuplicationsFilter';
import * as React from 'react';
import DuplicationsFilter, { Props } from './DuplicationsFilter';

export default function NewDuplicationsFilter(props) {
export default function NewDuplicationsFilter(props: Props) {
return <DuplicationsFilter {...props} property="new_duplications" className="leak-facet-box" />;
}

+ 0
- 71
server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.js Datei anzeigen

@@ -1,71 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel } from '../../../helpers/ratings';

export default class NewLinesFilter extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
query: PropTypes.object.isRequired,
isFavorite: PropTypes.bool,
organization: PropTypes.object,
property: PropTypes.string
};

static defaultProps = {
property: 'new_lines'
};

getFacetValueForOption(facet, option) {
const map = [
'*-1000.0',
'1000.0-10000.0',
'10000.0-100000.0',
'100000.0-500000.0',
'500000.0-*'
];
return facet[map[option - 1]];
}

renderOption(option) {
return <span>{getSizeRatingLabel(option)}</span>;
}

render() {
return (
<FilterContainer
property={this.props.property}
className="leak-facet-box"
options={[1, 2, 3, 4, 5]}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
highlightUnder={1}
header={<FilterHeader name={translate('projects.facets.new_lines')} />}
/>
);
}
}

+ 61
- 0
server/sonar-web/src/main/js/apps/projects/filters/NewLinesFilter.tsx Datei anzeigen

@@ -0,0 +1,61 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel } from '../../../helpers/ratings';
import { Facet } from './Filter';

export interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
}

export default function NewLinesFilter(props: Props) {
const { property = 'new_lines' } = props;

return (
<FilterContainer
property={property}
className="leak-facet-box"
options={[1, 2, 3, 4, 5]}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
highlightUnder={1}
header={<FilterHeader name={translate('projects.facets.new_lines')} />}
/>
);
}

function getFacetValueForOption(facet: Facet, option: number) {
const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*'];
return facet[map[option - 1]];
}

function renderOption(option: number) {
return <span>{getSizeRatingLabel(option)}</span>;
}

server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewMaintainabilityFilter.tsx Datei anzeigen

@@ -17,12 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import CodeSmellIcon from '../../../components/icons-components/CodeSmellIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';

export default function NewMaintainabilityFilter(props) {
interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function NewMaintainabilityFilter(props: Props) {
return (
<IssuesFilter
{...props}

server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewReliabilityFilter.tsx Datei anzeigen

@@ -17,12 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import BugIcon from '../../../components/icons-components/BugIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';

export default function NewReliabilityFilter(props) {
interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function NewReliabilityFilter(props: Props) {
return (
<IssuesFilter
{...props}

server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/NewSecurityFilter.tsx Datei anzeigen

@@ -17,12 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import VulnerabilityIcon from '../../../components/icons-components/VulnerabilityIcon';
import IssuesFilter from './IssuesFilter';
import { translate } from '../../../helpers/l10n';

export default function NewSecurityFilter(props) {
interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function NewSecurityFilter(props: Props) {
return (
<IssuesFilter
{...props}

server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.js → server/sonar-web/src/main/js/apps/projects/filters/QualityGateFilter.tsx Datei anzeigen

@@ -17,40 +17,39 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import PropTypes from 'prop-types';
import * as React from 'react';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import Level from '../../../components/ui/Level';
import { translate } from '../../../helpers/l10n';
import { Facet } from './Filter';

export default class QualityGateFilter extends React.PureComponent {
static propTypes = {
query: PropTypes.object.isRequired,
isFavorite: PropTypes.bool,
organization: PropTypes.object
};
export interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

getFacetValueForOption(facet, option) {
return facet[option];
}
export default function QualityGateFilter(props: Props) {
return (
<FilterContainer
property="gate"
options={['OK', 'WARN', 'ERROR']}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
header={<FilterHeader name={translate('projects.facets.quality_gate')} />}
/>
);
}

renderOption(option, selected) {
return <Level level={option} small={true} muted={!selected} />;
}
function getFacetValueForOption(facet: Facet, option: string) {
return facet[option];
}

render() {
return (
<FilterContainer
property="gate"
options={['OK', 'WARN', 'ERROR']}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
header={<FilterHeader name={translate('projects.facets.quality_gate')} />}
/>
);
}
function renderOption(option: string, selected: boolean) {
return <Level level={option} small={true} muted={!selected} />;
}

server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/ReliabilityFilter.tsx Datei anzeigen

@@ -17,9 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import IssuesFilter from './IssuesFilter';

export default function ReliabilityFilter(props) {
interface Props {
className?: string;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function ReliabilityFilter(props: Props) {
return <IssuesFilter {...props} name="Reliability" property="reliability" />;
}

server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.js → server/sonar-web/src/main/js/apps/projects/filters/SearchFilter.tsx Datei anzeigen

@@ -17,50 +17,39 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';

/*::
type Props = {
className?: string,
handleSearch: (userString?: string) => void,
query: { search?: string }
};
*/

/*::
type State = {
userQuery?: string
};
*/
interface Props {
className?: string;
handleSearch: (userString?: string) => void;
query: { search?: string | undefined };
}

export default class SearchFilter extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */
interface State {
userQuery?: string;
}

constructor(props /*: Props */) {
export default class SearchFilter extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
userQuery: props.query.search
};
this.state = { userQuery: props.query.search };
}

componentWillReceiveProps(nextProps /*: Props */) {
componentWillReceiveProps(nextProps: Props) {
if (
this.props.query.search === this.state.userQuery &&
nextProps.query.search !== this.props.query.search
) {
this.setState({
userQuery: nextProps.query.search || ''
});
this.setState({ userQuery: nextProps.query.search || '' });
}
}

handleQueryChange = ({ target } /*: { target: HTMLInputElement } */) => {
this.setState({ userQuery: target.value });
if (!target.value || target.value.length >= 2) {
this.props.handleSearch(target.value);
handleQueryChange = (event: React.SyntheticEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
this.setState({ userQuery: value });
if (!value || value.length >= 2) {
this.props.handleSearch(value);
}
};


server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.js → server/sonar-web/src/main/js/apps/projects/filters/SearchFilterContainer.tsx Datei anzeigen

@@ -17,36 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import { withRouter } from 'react-router';
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { debounce } from 'lodash';
import { getFilterUrl } from './utils';
import SearchFilter from './SearchFilter';

/*::
type Props = {|
className?: string,
query: { search?: string },
router: { push: ({ pathname: string }) => void },
isFavorite?: boolean,
organization?: {}
|};
*/
interface Props {
className?: string;
query: { search?: string };
isFavorite?: boolean;
organization?: { key: string };
}

class SearchFilterContainer extends React.PureComponent {
/*:: handleSearch: (userQuery?: string) => void; */
/*:: props: Props; */
export default class SearchFilterContainer extends React.PureComponent<Props> {
static contextTypes = {
router: PropTypes.object.isRequired
};

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.handleSearch = debounce(this.handleSearch.bind(this), 250);
this.handleSearch = debounce(this.handleSearch, 250);
}

handleSearch(userQuery /*: ?string */) {
const path = getFilterUrl(this.props, { search: userQuery || null });
this.props.router.push(path);
}
handleSearch = (userQuery?: string) => {
const path = getFilterUrl(this.props, { search: userQuery });
this.context.router.push(path);
};

render() {
return (
@@ -58,5 +55,3 @@ class SearchFilterContainer extends React.PureComponent {
);
}
}

export default withRouter(SearchFilterContainer);

server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.js → server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterFooter.tsx Datei anzeigen

@@ -17,48 +17,47 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import Select from 'react-select';
import * as React from 'react';
import * as Select from 'react-select';
import * as PropTypes from 'prop-types';
import { getFilterUrl } from './utils';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
property: string,
query: {},
options: Array<{ label: string, value: string }>,
router: { push: ({ pathname: string, query?: {} }) => void },
onInputChange?: string => void,
onOpen?: void => void,
isLoading?: boolean,
isFavorite?: boolean,
organization?: {}
};
*/
interface Props {
property: string;
query: { [x: string]: any };
options: Array<{ label: string; value: string }>;
onInputChange?: (query: string) => void;
onOpen?: () => void;
isLoading?: boolean;
isFavorite?: boolean;
organization?: { key: string };
}

export default class SearchableFilterFooter extends React.PureComponent {
/*:: props: Props; */
export default class SearchableFilterFooter extends React.PureComponent<Props> {
static contextTypes = {
router: PropTypes.object.isRequired
};

handleOptionChange /*: ({ value: string }) => void */ = ({ value }) => {
handleOptionChange = ({ value }: { value: string }) => {
const urlOptions = (this.props.query[this.props.property] || []).concat(value).join(',');
const path = getFilterUrl(this.props, { [this.props.property]: urlOptions });
this.props.router.push(path);
this.context.router.push(path);
};

render() {
return (
<div className="search-navigator-facet-footer projects-facet-footer">
<Select
onChange={this.handleOptionChange}
className="input-super-large"
placeholder={translate('search_verb')}
clearable={false}
searchable={true}
isLoading={this.props.isLoading}
onChange={this.handleOptionChange}
onInputChange={this.props.onInputChange}
onOpen={this.props.onOpen}
isLoading={this.props.isLoading}
options={this.props.options}
placeholder={translate('search_verb')}
searchable={true}
/>
</div>
);

+ 0
- 34
server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.js Datei anzeigen

@@ -1,34 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import { translate } from '../../../helpers/l10n';

export default class SearchableFilterOption extends React.PureComponent {
static propTypes = {
optionKey: PropTypes.string.isRequired,
option: PropTypes.object
};

render() {
const optionName = this.props.option ? this.props.option.name : this.props.optionKey;
return <span>{this.props.optionKey !== '<null>' ? optionName : translate('unknown')}</span>;
}
}

server/sonar-web/src/main/js/apps/projects/filters/FilterHeader.js → server/sonar-web/src/main/js/apps/projects/filters/SearchableFilterOption.tsx Datei anzeigen

@@ -17,25 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
name: string,
children?: React.Element<*>
};
*/

export default class FilterHeader extends React.PureComponent {
/*:: props: Props; */
interface Props {
option?: { name: string };
optionKey: string;
}

render() {
return (
<div className="search-navigator-facet-header projects-facet-header">
{this.props.name}
{this.props.children}
</div>
);
}
export default function SearchableFilterOption(props: Props) {
const optionName = props.option ? props.option.name : props.optionKey;
return <span>{props.optionKey !== '<null>' ? optionName : translate('unknown')}</span>;
}

server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.js → server/sonar-web/src/main/js/apps/projects/filters/SecurityFilter.tsx Datei anzeigen

@@ -17,9 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import React from 'react';
import * as React from 'react';
import IssuesFilter from './IssuesFilter';

export default function SecurityFilter(props) {
interface Props {
className?: string;
headerDetail?: React.ReactNode;
isFavorite?: boolean;
organization?: { key: string };
query: { [x: string]: any };
}

export default function SecurityFilter(props: Props) {
return <IssuesFilter {...props} name="Security" property="security" />;
}

+ 0
- 77
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.js Datei anzeigen

@@ -1,77 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 React from 'react';
import PropTypes from 'prop-types';
import FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';

export default class SizeFilter extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
query: PropTypes.object.isRequired,
isFavorite: PropTypes.bool,
organization: PropTypes.object,
property: PropTypes.string
};

static defaultProps = {
property: 'size'
};

getFacetValueForOption(facet, option) {
const map = [
'*-1000.0',
'1000.0-10000.0',
'10000.0-100000.0',
'100000.0-500000.0',
'500000.0-*'
];
return facet[map[option - 1]];
}

renderOption(option, selected) {
return (
<span>
<SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected} />
<span className="spacer-left">{getSizeRatingLabel(option)}</span>
</span>
);
}

render() {
return (
<FilterContainer
property={this.props.property}
className={this.props.className}
options={[1, 2, 3, 4, 5]}
query={this.props.query}
renderOption={this.renderOption}
isFavorite={this.props.isFavorite}
organization={this.props.organization}
getFacetValueForOption={this.getFacetValueForOption}
highlightUnder={1}
header={<FilterHeader name={translate('metric_domain.Size')} />}
/>
);
}
}

+ 67
- 0
server/sonar-web/src/main/js/apps/projects/filters/SizeFilter.tsx Datei anzeigen

@@ -0,0 +1,67 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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 FilterContainer from './FilterContainer';
import FilterHeader from './FilterHeader';
import SizeRating from '../../../components/ui/SizeRating';
import { translate } from '../../../helpers/l10n';
import { getSizeRatingLabel, getSizeRatingAverageValue } from '../../../helpers/ratings';
import { Facet } from './Filter';

export interface Props {
className?: string;
isFavorite?: boolean;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
}

export default function SizeFilter(props: Props) {
const { property = 'size' } = props;

return (
<FilterContainer
property={property}
className={props.className}
options={[1, 2, 3, 4, 5]}
query={props.query}
renderOption={renderOption}
isFavorite={props.isFavorite}
organization={props.organization}
getFacetValueForOption={getFacetValueForOption}
highlightUnder={1}
header={<FilterHeader name={translate('metric_domain.Size')} />}
/>
);
}

function getFacetValueForOption(facet: Facet, option: number) {
const map = ['*-1000.0', '1000.0-10000.0', '10000.0-100000.0', '100000.0-500000.0', '500000.0-*'];
return facet[map[option - 1]];
}

function renderOption(option: number, selected: boolean) {
return (
<span>
<SizeRating value={getSizeRatingAverageValue(option)} small={true} muted={!selected} />
<span className="spacer-left">{getSizeRatingLabel(option)}</span>
</span>
);
}

server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.js → server/sonar-web/src/main/js/apps/projects/filters/TagsFilter.tsx Datei anzeigen

@@ -17,66 +17,63 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//@flow
import React from 'react';
import * as React from 'react';
import { debounce, difference, sortBy, size } from 'lodash';
import Filter from './Filter';
import Filter, { Facet } from './Filter';
import FilterHeader from './FilterHeader';
import SearchableFilterFooter from './SearchableFilterFooter';
import SearchableFilterOption from './SearchableFilterOption';
import { searchProjectTags } from '../../../api/components';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
query: {},
router: { push: ({ pathname: string, query?: {} }) => void },
value?: Array<string>,
facet?: {},
isFavorite?: boolean,
organization?: {},
maxFacetValue?: number
};
*/
interface Props {
facet?: Facet;
isFavorite?: boolean;
maxFacetValue?: number;
organization?: { key: string };
property?: string;
query: { [x: string]: any };
value?: string[];
}

/*::
type State = {
isLoading: boolean,
search: string,
tags: Array<string>
};
*/
interface State {
isLoading: boolean;
search: string;
tags: string[];
}

const LIST_SIZE = 10;

export default class TagsFilter extends React.PureComponent {
/*:: props: Props; */
/*:: state: State; */
/*:: property: string; */
export default class TagsFilter extends React.PureComponent<Props, State> {
mounted: boolean;

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.state = {
isLoading: false,
search: '',
tags: []
};
this.property = 'tags';
this.handleSearch = debounce(this.handleSearch.bind(this), 250);
this.handleSearch = debounce(this.handleSearch, 250);
}

getSearchOptions(
facet /*: ?{} */,
tags /*: Array<string> */
) /*: Array<{ label: string, value: string }> */ {
let tagsCopy = [...tags];
if (facet) {
tagsCopy = difference(tagsCopy, Object.keys(facet));
componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

getSearchOptions = () => {
let tagsCopy = [...this.state.tags];
if (this.props.facet) {
tagsCopy = difference(tagsCopy, Object.keys(this.props.facet));
}
return tagsCopy.slice(0, LIST_SIZE).map(tag => ({ label: tag, value: tag }));
}
};

handleSearch = (search /*: ?string */) => {
handleSearch = (search?: string) => {
if (search !== this.state.search) {
search = search || '';
this.setState({ search, isLoading: true });
@@ -84,23 +81,26 @@ export default class TagsFilter extends React.PureComponent {
q: search,
ps: size(this.props.facet || {}) + LIST_SIZE
}).then(result => {
this.setState({ isLoading: false, tags: result.tags });
if (this.mounted) {
this.setState({ isLoading: false, tags: result.tags });
}
});
}
};

getSortedOptions(facet /*: {} */ = {}) {
return sortBy(Object.keys(facet), [option => -facet[option], option => option]);
}
getSortedOptions = (facet: Facet = {}) =>
sortBy(Object.keys(facet), [(option: string) => -facet[option], (option: string) => option]);

getFacetValueForOption = (facet /*: {} */, option /*: string */) => facet[option];
getFacetValueForOption = (facet: Facet = {}, option: string) => facet[option];

renderOption = (option /*: string */) => <SearchableFilterOption optionKey={option} />;
renderOption = (option: string) => <SearchableFilterOption optionKey={option} />;

render() {
const { property = 'tags' } = this.props;

return (
<Filter
property={this.property}
property={property}
options={this.getSortedOptions(this.props.facet)}
query={this.props.query}
renderOption={this.renderOption}
@@ -113,15 +113,14 @@ export default class TagsFilter extends React.PureComponent {
header={<FilterHeader name={translate('projects.facets.tags')} />}
footer={
<SearchableFilterFooter
property={this.property}
query={this.props.query}
options={this.getSearchOptions(this.props.facet, this.state.tags)}
isFavorite={this.props.isFavorite}
isLoading={this.state.isLoading}
onOpen={this.handleSearch}
onInputChange={this.handleSearch}
isFavorite={this.props.isFavorite}
onOpen={this.handleSearch}
organization={this.props.organization}
router={this.props.router}
options={this.getSearchOptions()}
property={property}
query={this.props.query}
/>
}
/>

server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.js → server/sonar-web/src/main/js/apps/projects/filters/TagsFilterContainer.ts Datei anzeigen

@@ -18,16 +18,15 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import TagsFilter from './TagsFilter';
import {
getProjectsAppFacetByProperty,
getProjectsAppMaxFacetValue
} from '../../../store/rootReducer';

const mapStateToProps = (state, ownProps) => ({
const mapStateToProps = (state: any, ownProps: any) => ({
value: ownProps.query['tags'],
facet: getProjectsAppFacetByProperty(state, 'tags'),
maxFacetValue: getProjectsAppMaxFacetValue(state)
});
export default connect(mapStateToProps)(withRouter(TagsFilter));
export default connect<any, any, any>(mapStateToProps)(TagsFilter);

+ 38
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/CoverageFilter-test.tsx Datei anzeigen

@@ -0,0 +1,38 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 CoverageFilter from '../CoverageFilter';

it('renders', () => {
const wrapper = shallow(<CoverageFilter query={{}} />);
expect(wrapper).toMatchSnapshot();

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(
getFacetValueForOption(
{ '80.0-*': 1, '70.0-80.0': 42, '50.0-70.0': 14, '30.0-50.0': 13, '*-30.0': 8, NO_DATA: 3 },
2
)
).toBe(42);
});

+ 39
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/DuplicationsFilter-test.tsx Datei anzeigen

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 DuplicationsFilter from '../DuplicationsFilter';

it('renders', () => {
const wrapper = shallow(<DuplicationsFilter query={{}} />);
expect(wrapper).toMatchSnapshot();

const renderOption = wrapper.prop('renderOption');
expect(renderOption(2, false)).toMatchSnapshot();
expect(renderOption(6, true)).toMatchSnapshot();

const getFacetValueForOption = wrapper.prop('getFacetValueForOption');
expect(
getFacetValueForOption(
{ '*-3.0': 1, '3.0-5.0': 42, '5.0-10.0': 14, '10.0-20.0': 13, '20.0-*': 8, NO_DATA: 3 },
2
)
).toBe(42);
});

+ 73
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx Datei anzeigen

@@ -0,0 +1,73 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 Filter from '../Filter';

it('renders', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('renders header and footer', () => {
expect(shallowRender({ header: <header />, footer: <footer /> })).toMatchSnapshot();
});

it('renders no results', () => {
expect(shallowRender({ options: [] })).toMatchSnapshot();
});

it('highlights under', () => {
expect(shallowRender({ highlightUnder: 1 })).toMatchSnapshot();
});

it('renders selected', () => {
expect(shallowRender({ value: 2 })).toMatchSnapshot();
});

it('hightlights under selected', () => {
expect(shallowRender({ highlightUnder: 1, value: 2 })).toMatchSnapshot();
});

it('renders multiple selected', () => {
expect(shallowRender({ value: [1, 2] })).toMatchSnapshot();
});

it('renders facet bar chart', () => {
expect(
shallowRender({
getFacetValueForOption: (facet: any, option: any) => facet[option],
facet: { a: 17, b: 15, c: 24 },
maxFacetValue: 24,
options: ['a', 'b', 'c']
})
).toMatchSnapshot();
});

function shallowRender(props?: any) {
return shallow(
<Filter
options={[1, 2, 3]}
property="foo"
query={{}}
renderOption={option => option}
{...props}
/>
);
}

+ 0
- 0
server/sonar-web/src/main/js/apps/projects/filters/__tests__/FilterHeader-test.tsx Datei anzeigen


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

Laden…
Abbrechen
Speichern