aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/component-measures/details/history/MeasureHistory.js3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/App.js10
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/AppContainer.js5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/redirects.js2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/utils.js118
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js52
-rw-r--r--server/sonar-web/src/main/js/apps/overview/events/Analysis.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/actions-test.js.snap85
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js106
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/actions.js119
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/Event.js40
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/Events.js14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js37
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js29
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js173
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js (renamed from server/sonar-web/src/main/js/apps/overview/actions.js)19
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js45
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js14
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js40
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js41
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js18
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js48
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js4
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js48
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/routes.js2
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/types.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddVersionForm.js)34
-rw-r--r--server/sonar-web/src/main/js/apps/projectActivity/utils.js (renamed from server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeVersionForm.js)35
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.js5
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageHeader.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/store/utils.js2
36 files changed, 604 insertions, 567 deletions
diff --git a/server/sonar-web/src/main/js/apps/component-measures/details/history/MeasureHistory.js b/server/sonar-web/src/main/js/apps/component-measures/details/history/MeasureHistory.js
index af8e4bde73c..c653d64e7b8 100644
--- a/server/sonar-web/src/main/js/apps/component-measures/details/history/MeasureHistory.js
+++ b/server/sonar-web/src/main/js/apps/component-measures/details/history/MeasureHistory.js
@@ -88,7 +88,8 @@ export default class MeasureHistory extends React.PureComponent {
return Promise.resolve([]);
}
- return getProjectActivity(this.props.component.key, {
+ return getProjectActivity({
+ project: this.props.component.key,
category: 'VERSION'
}).then(({ analyses }) => {
const events = analyses.map(analysis => {
diff --git a/server/sonar-web/src/main/js/apps/issues/components/App.js b/server/sonar-web/src/main/js/apps/issues/components/App.js
index d827a0d1f1f..e003d9ec0ea 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/App.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/App.js
@@ -58,15 +58,19 @@ import EmptySearch from '../../../components/common/EmptySearch';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { scrollToElement } from '../../../helpers/scrolling';
import type { Issue } from '../../../components/issue/types';
+import type { RawQuery } from '../../../helpers/query';
import '../styles.css';
export type Props = {
component?: Component,
currentUser: CurrentUser,
- fetchIssues: ({}) => Promise<*>,
- location: { pathname: string, query: { [string]: string } },
+ fetchIssues: (query: RawQuery) => Promise<*>,
+ location: { pathname: string, query: RawQuery },
onRequestFail: Error => void,
- router: { push: ({}) => void, replace: ({}) => void }
+ router: {
+ push: ({ pathname: string, query?: RawQuery }) => void,
+ replace: ({ pathname: string, query?: RawQuery }) => void
+ }
};
export type State = {
diff --git a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js
index 3b2ce078438..0ce4f8bb598 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js
+++ b/server/sonar-web/src/main/js/apps/issues/components/AppContainer.js
@@ -29,8 +29,7 @@ import { getOrganizations } from '../../../api/organizations';
import { receiveOrganizations } from '../../../store/organizations/duck';
import { searchIssues } from '../../../api/issues';
import { parseIssueFromResponse } from '../../../helpers/issues';
-
-type Query = { [string]: string };
+import type { RawQuery } from '../../../helpers/query';
const mapStateToProps = (state, ownProps) => ({
component: ownProps.location.query.id
@@ -51,7 +50,7 @@ const fetchIssueOrganizations = issues => dispatch => {
);
};
-const fetchIssues = (query: Query) => dispatch =>
+const fetchIssues = (query: RawQuery) => dispatch =>
searchIssues({ ...query, additionalFields: '_all' })
.then(response => {
const parsedIssues = response.issues.map(issue =>
diff --git a/server/sonar-web/src/main/js/apps/issues/redirects.js b/server/sonar-web/src/main/js/apps/issues/redirects.js
index 3efc1af64be..a66bb570a7b 100644
--- a/server/sonar-web/src/main/js/apps/issues/redirects.js
+++ b/server/sonar-web/src/main/js/apps/issues/redirects.js
@@ -19,7 +19,7 @@
*/
// @flow
import { parseQuery, areMyIssuesSelected, serializeQuery } from './utils';
-import type { RawQuery } from './utils';
+import type { RawQuery } from '../../helpers/query';
const parseHash = (hash: string): RawQuery => {
const query: RawQuery = {};
diff --git a/server/sonar-web/src/main/js/apps/issues/utils.js b/server/sonar-web/src/main/js/apps/issues/utils.js
index d33defd8135..72451971f7a 100644
--- a/server/sonar-web/src/main/js/apps/issues/utils.js
+++ b/server/sonar-web/src/main/js/apps/issues/utils.js
@@ -18,11 +18,19 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
-import { isNil, omitBy } from 'lodash';
import { searchMembers } from '../../api/organizations';
import { searchUsers } from '../../api/users';
-
-export type RawQuery = { [string]: string };
+import {
+ queriesEqual,
+ cleanQuery,
+ parseAsBoolean,
+ parseAsFacetMode,
+ parseAsArray,
+ parseAsString,
+ serializeString,
+ serializeStringArray
+} from '../../helpers/query';
+import type { RawQuery } from '../../helpers/query';
export type Query = {|
assigned: boolean,
@@ -56,112 +64,70 @@ export type Paging = {
total: number
};
-const parseAsBoolean = (value: ?string, defaultValue: boolean = true): boolean =>
- (value === 'false' ? false : value === 'true' ? true : defaultValue);
-
-const parseAsString = (value: ?string): string => value || '';
-
-const parseAsStringArray = (value: ?string): Array<string> => (value ? value.split(',') : []);
-
-const parseAsFacetMode = (facetMode: string) =>
- (facetMode === 'debt' || facetMode === 'effort' ? 'effort' : 'count');
-
// allow sorting by CREATION_DATE only
const parseAsSort = (sort: string): string => (sort === 'CREATION_DATE' ? 'CREATION_DATE' : '');
export const parseQuery = (query: RawQuery): Query => ({
assigned: parseAsBoolean(query.assigned),
- assignees: parseAsStringArray(query.assignees),
- authors: parseAsStringArray(query.authors),
+ assignees: parseAsArray(query.assignees, parseAsString),
+ authors: parseAsArray(query.authors, parseAsString),
createdAfter: parseAsString(query.createdAfter),
createdAt: parseAsString(query.createdAt),
createdBefore: parseAsString(query.createdBefore),
createdInLast: parseAsString(query.createdInLast),
- directories: parseAsStringArray(query.directories),
+ directories: parseAsArray(query.directories, parseAsString),
facetMode: parseAsFacetMode(query.facetMode),
- files: parseAsStringArray(query.fileUuids),
- issues: parseAsStringArray(query.issues),
- languages: parseAsStringArray(query.languages),
- modules: parseAsStringArray(query.moduleUuids),
- projects: parseAsStringArray(query.projectUuids),
+ files: parseAsArray(query.fileUuids, parseAsString),
+ issues: parseAsArray(query.issues, parseAsString),
+ languages: parseAsArray(query.languages, parseAsString),
+ modules: parseAsArray(query.moduleUuids, parseAsString),
+ projects: parseAsArray(query.projectUuids, parseAsString),
resolved: parseAsBoolean(query.resolved),
- resolutions: parseAsStringArray(query.resolutions),
- rules: parseAsStringArray(query.rules),
+ resolutions: parseAsArray(query.resolutions, parseAsString),
+ rules: parseAsArray(query.rules, parseAsString),
sort: parseAsSort(query.s),
- severities: parseAsStringArray(query.severities),
+ severities: parseAsArray(query.severities, parseAsString),
sinceLeakPeriod: parseAsBoolean(query.sinceLeakPeriod, false),
- statuses: parseAsStringArray(query.statuses),
- tags: parseAsStringArray(query.tags),
- types: parseAsStringArray(query.types)
+ statuses: parseAsArray(query.statuses, parseAsString),
+ tags: parseAsArray(query.tags, parseAsString),
+ types: parseAsArray(query.types, parseAsString)
});
export const getOpen = (query: RawQuery) => query.open;
export const areMyIssuesSelected = (query: RawQuery): boolean => query.myIssues === 'true';
-const serializeString = (value: string): ?string => value || undefined;
-
-const serializeValue = (value: Array<string>): ?string => (value.length ? value.join() : undefined);
-
export const serializeQuery = (query: Query): RawQuery => {
const filter = {
assigned: query.assigned ? undefined : 'false',
- assignees: serializeValue(query.assignees),
- authors: serializeValue(query.authors),
+ assignees: serializeStringArray(query.assignees),
+ authors: serializeStringArray(query.authors),
createdAfter: serializeString(query.createdAfter),
createdAt: serializeString(query.createdAt),
createdBefore: serializeString(query.createdBefore),
createdInLast: serializeString(query.createdInLast),
- directories: serializeValue(query.directories),
+ directories: serializeStringArray(query.directories),
facetMode: query.facetMode === 'effort' ? serializeString(query.facetMode) : undefined,
- fileUuids: serializeValue(query.files),
- issues: serializeValue(query.issues),
- languages: serializeValue(query.languages),
- moduleUuids: serializeValue(query.modules),
- projectUuids: serializeValue(query.projects),
+ fileUuids: serializeStringArray(query.files),
+ issues: serializeStringArray(query.issues),
+ languages: serializeStringArray(query.languages),
+ moduleUuids: serializeStringArray(query.modules),
+ projectUuids: serializeStringArray(query.projects),
resolved: query.resolved ? undefined : 'false',
- resolutions: serializeValue(query.resolutions),
+ resolutions: serializeStringArray(query.resolutions),
s: serializeString(query.sort),
- severities: serializeValue(query.severities),
+ severities: serializeStringArray(query.severities),
sinceLeakPeriod: query.sinceLeakPeriod ? 'true' : undefined,
- statuses: serializeValue(query.statuses),
- rules: serializeValue(query.rules),
- tags: serializeValue(query.tags),
- types: serializeValue(query.types)
+ statuses: serializeStringArray(query.statuses),
+ rules: serializeStringArray(query.rules),
+ tags: serializeStringArray(query.tags),
+ types: serializeStringArray(query.types)
};
- return omitBy(filter, isNil);
-};
-
-const areArraysEqual = (a: Array<string>, b: Array<string>) => {
- if (a.length !== b.length) {
- return false;
- }
- for (let i = 0; i < a.length; i++) {
- if (a[i] !== b[i]) {
- return false;
- }
- }
- return true;
+ return cleanQuery(filter);
};
-export const areQueriesEqual = (a: RawQuery, b: RawQuery) => {
- const parsedA: Query = parseQuery(a);
- const parsedB: Query = parseQuery(b);
-
- const keysA = Object.keys(parsedA);
- const keysB = Object.keys(parsedB);
-
- if (keysA.length !== keysB.length) {
- return false;
- }
-
- return keysA.every(
- key =>
- (Array.isArray(parsedA[key]) && Array.isArray(parsedB[key])
- ? areArraysEqual(parsedA[key], parsedB[key])
- : parsedA[key] === parsedB[key])
- );
-};
+export const areQueriesEqual = (a: RawQuery, b: RawQuery) =>
+ queriesEqual(parseQuery(a), parseQuery(b));
type RawFacet = {
property: string,
diff --git a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
index cd4ef7c68e4..6500ae188be 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/AnalysesList.js
@@ -20,26 +20,27 @@
// @flow
import React from 'react';
import { Link } from 'react-router';
-import { connect } from 'react-redux';
import Analysis from './Analysis';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
+import { getProjectActivity } from '../../../api/projectActivity';
import { translate } from '../../../helpers/l10n';
-import { fetchRecentProjectActivity } from '../actions';
-import { getProjectActivity } from '../../../store/rootReducer';
-import { getAnalyses } from '../../../store/projectActivity/duck';
+import type { Analysis as AnalysisType } from '../../projectActivity/types';
type Props = {
- analyses?: Array<*>,
- project: string,
- fetchRecentProjectActivity: (project: string) => Promise<*>
+ project: string
};
-class AnalysesList extends React.PureComponent {
+type State = {
+ analyses: Array<AnalysisType>,
+ loading: boolean
+};
+
+const PAGE_SIZE = 5;
+
+export default class AnalysesList extends React.PureComponent {
mounted: boolean;
props: Props;
-
- state = {
- loading: true
- };
+ state: State = { analyses: [], loading: true };
componentDidMount() {
this.mounted = true;
@@ -58,14 +59,16 @@ class AnalysesList extends React.PureComponent {
fetchData() {
this.setState({ loading: true });
- this.props.fetchRecentProjectActivity(this.props.project).then(() => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
- });
+ getProjectActivity({ project: this.props.project, ps: PAGE_SIZE })
+ .then(({ analyses }) => {
+ if (this.mounted) {
+ this.setState({ analyses, loading: false });
+ }
+ })
+ .catch(throwGlobalError);
}
- renderList(analyses) {
+ renderList(analyses: Array<AnalysisType>) {
if (!analyses.length) {
return (
<p className="spacer-top note">
@@ -82,10 +85,9 @@ class AnalysesList extends React.PureComponent {
}
render() {
- const { analyses } = this.props;
- const { loading } = this.state;
+ const { analyses, loading } = this.state;
- if (loading || !analyses) {
+ if (loading) {
return null;
}
@@ -106,11 +108,3 @@ class AnalysesList extends React.PureComponent {
);
}
}
-
-const mapStateToProps = (state, ownProps: Props) => ({
- analyses: getAnalyses(getProjectActivity(state), ownProps.project)
-});
-
-const mapDispatchToProps = { fetchRecentProjectActivity };
-
-export default connect(mapStateToProps, mapDispatchToProps)(AnalysesList);
diff --git a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js b/server/sonar-web/src/main/js/apps/overview/events/Analysis.js
index 582b64b64a4..959338bb2df 100644
--- a/server/sonar-web/src/main/js/apps/overview/events/Analysis.js
+++ b/server/sonar-web/src/main/js/apps/overview/events/Analysis.js
@@ -22,7 +22,7 @@ import Events from '../../projectActivity/components/Events';
import FormattedDate from '../../../components/ui/FormattedDate';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
import { translate } from '../../../helpers/l10n';
-import type { Analysis as AnalysisType } from '../../../store/projectActivity/duck';
+import type { Analysis as AnalysisType } from '../../projectActivity/types';
export default function Analysis(props: { analysis: AnalysisType }) {
const { analysis } = props;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/actions-test.js.snap b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/actions-test.js.snap
new file mode 100644
index 00000000000..2e1d7d96c09
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/__snapshots__/actions-test.js.snap
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`addCustomEvent should correctly add a custom event 1`] = `
+Object {
+ "date": "2016-10-27T12:21:15+0200",
+ "events": Array [
+ Object {
+ "category": "Custom",
+ "key": "Enew",
+ "name": "Foo",
+ },
+ ],
+ "key": "A2",
+}
+`;
+
+exports[`changeEvent should correctly update an event 1`] = `
+Object {
+ "date": "2016-10-27T16:33:50+0200",
+ "events": Array [
+ Object {
+ "category": "VERSION",
+ "key": "E1",
+ "name": "changed",
+ },
+ ],
+ "key": "A1",
+}
+`;
+
+exports[`deleteAnalysis should correctly delete an analyses 1`] = `
+Array [
+ Object {
+ "date": "2016-10-27T12:21:15+0200",
+ "events": Array [],
+ "key": "A2",
+ },
+ Object {
+ "date": "2016-10-26T12:17:29+0200",
+ "events": Array [
+ Object {
+ "category": "OTHER",
+ "key": "E2",
+ "name": "foo",
+ },
+ Object {
+ "category": "OTHER",
+ "key": "E3",
+ "name": "foo",
+ },
+ ],
+ "key": "A3",
+ },
+]
+`;
+
+exports[`deleteEvent should correctly remove an event 1`] = `
+Object {
+ "date": "2016-10-27T16:33:50+0200",
+ "events": Array [],
+ "key": "A1",
+}
+`;
+
+exports[`deleteEvent should correctly remove an event 2`] = `
+Object {
+ "date": "2016-10-27T12:21:15+0200",
+ "events": Array [],
+ "key": "A2",
+}
+`;
+
+exports[`deleteEvent should correctly remove an event 3`] = `
+Object {
+ "date": "2016-10-26T12:17:29+0200",
+ "events": Array [
+ Object {
+ "category": "OTHER",
+ "key": "E3",
+ "name": "foo",
+ },
+ ],
+ "key": "A3",
+}
+`;
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
new file mode 100644
index 00000000000..896a172c9a4
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/projectActivity/__tests__/actions-test.js
@@ -0,0 +1,106 @@
+/*
+ * 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 actions from '../actions';
+
+const ANALYSES = [
+ {
+ key: 'A1',
+ date: '2016-10-27T16:33:50+0200',
+ events: [
+ {
+ key: 'E1',
+ category: 'VERSION',
+ name: '6.5-SNAPSHOT'
+ }
+ ]
+ },
+ {
+ key: 'A2',
+ date: '2016-10-27T12:21:15+0200',
+ events: []
+ },
+ {
+ key: 'A3',
+ date: '2016-10-26T12:17:29+0200',
+ events: [
+ {
+ key: 'E2',
+ category: 'OTHER',
+ name: 'foo'
+ },
+ {
+ key: 'E3',
+ category: 'OTHER',
+ name: 'foo'
+ }
+ ]
+ }
+];
+
+const newEvent = {
+ key: 'Enew',
+ name: 'Foo',
+ category: 'Custom'
+};
+
+it('should never throw when there is no analyses', () => {
+ expect(actions.addCustomEvent('A1', newEvent)({})).toBeUndefined();
+ expect(actions.deleteEvent('A1', newEvent)({})).toBeUndefined();
+ expect(actions.changeEvent('A1', newEvent)({})).toBeUndefined();
+ expect(actions.deleteAnalysis('Anew')({})).toBeUndefined();
+});
+
+describe('addCustomEvent', () => {
+ it('should correctly add a custom event', () => {
+ expect(
+ actions.addCustomEvent('A2', newEvent)({ analyses: ANALYSES }).analyses[1]
+ ).toMatchSnapshot();
+ expect(
+ actions.addCustomEvent('A1', newEvent)({ analyses: ANALYSES }).analyses[0].events
+ ).toContain(newEvent);
+ });
+});
+
+describe('deleteEvent', () => {
+ it('should correctly remove an event', () => {
+ expect(actions.deleteEvent('A1', 'E1')({ analyses: ANALYSES }).analyses[0]).toMatchSnapshot();
+ expect(actions.deleteEvent('A2', 'E1')({ analyses: ANALYSES }).analyses[1]).toMatchSnapshot();
+ expect(actions.deleteEvent('A3', 'E2')({ analyses: ANALYSES }).analyses[2]).toMatchSnapshot();
+ });
+});
+
+describe('changeEvent', () => {
+ it('should correctly update an event', () => {
+ expect(
+ actions.changeEvent('A1', { key: 'E1', name: 'changed' })({ analyses: ANALYSES }).analyses[0]
+ ).toMatchSnapshot();
+ expect(
+ actions.changeEvent('A2', { key: 'E2' })({ analyses: ANALYSES }).analyses[1].events
+ ).toHaveLength(0);
+ });
+});
+
+describe('deleteAnalysis', () => {
+ it('should correctly delete an analyses', () => {
+ expect(actions.deleteAnalysis('A1')({ analyses: ANALYSES }).analyses).toMatchSnapshot();
+ expect(actions.deleteAnalysis('A5')({ analyses: ANALYSES }).analyses).toHaveLength(3);
+ expect(actions.deleteAnalysis('A2')({ analyses: ANALYSES }).analyses).toHaveLength(2);
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/actions.js b/server/sonar-web/src/main/js/apps/projectActivity/actions.js
index 60a2dbcb480..e4e36ff0f85 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/actions.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/actions.js
@@ -18,81 +18,44 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
-import * as api from '../../api/projectActivity';
-import {
- receiveProjectActivity,
- addEvent,
- deleteEvent as deleteEventAction,
- changeEvent as changeEventAction,
- deleteAnalysis as deleteAnalysisAction,
- getPaging
-} from '../../store/projectActivity/duck';
-import { onFail } from '../../store/rootActions';
-import { getProjectActivity } from '../../store/rootReducer';
-
-const rejectOnFail = (dispatch: Function) => (error: Object) => {
- onFail(dispatch)(error);
- return Promise.reject();
-};
-
-export const fetchProjectActivity = (project: string, filter: ?string) => (
- dispatch: Function
-): void => {
- api
- .getProjectActivity(project, { category: filter })
- .then(
- ({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
- onFail(dispatch)
- );
-};
-
-export const fetchMoreProjectActivity = (project: string, filter: ?string) => (
- dispatch: Function,
- getState: Function
-): void => {
- const projectActivity = getProjectActivity(getState());
- const { pageIndex } = getPaging(projectActivity, project);
-
- api
- .getProjectActivity(project, { category: filter, pageIndex: pageIndex + 1 })
- .then(
- ({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
- onFail(dispatch)
- );
-};
-
-export const addCustomEvent = (analysis: string, name: string, category?: string) => (
- dispatch: Function
-): Promise<*> => {
- return api
- .createEvent(analysis, name, category)
- .then(({ analysis, ...event }) => dispatch(addEvent(analysis, event)), rejectOnFail(dispatch));
-};
-
-export const deleteEvent = (analysis: string, event: string) => (
- dispatch: Function
-): Promise<*> => {
- return api
- .deleteEvent(event)
- .then(() => dispatch(deleteEventAction(analysis, event)), rejectOnFail(dispatch));
-};
-
-export const addVersion = (analysis: string, version: string) => (
- dispatch: Function
-): Promise<*> => {
- return dispatch(addCustomEvent(analysis, version, 'VERSION'));
-};
-
-export const changeEvent = (event: string, name: string) => (dispatch: Function): Promise<*> => {
- return api
- .changeEvent(event, name)
- .then(() => dispatch(changeEventAction(event, { name })), rejectOnFail(dispatch));
-};
-
-export const deleteAnalysis = (project: string, analysis: string) => (
- dispatch: Function
-): Promise<*> => {
- return api
- .deleteAnalysis(analysis)
- .then(() => dispatch(deleteAnalysisAction(project, analysis)), rejectOnFail(dispatch));
-};
+import type { Event } from './types';
+import type { State } from './components/ProjectActivityApp';
+
+export const addCustomEvent = (analysis: string, event: Event) => (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return { ...item, events: [...item.events, event] };
+ })
+});
+
+export const deleteEvent = (analysis: string, event: string) => (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return {
+ ...item,
+ events: item.events.filter(eventItem => eventItem.key !== event)
+ };
+ })
+});
+
+export const changeEvent = (analysis: string, event: Event) => (state: State) => ({
+ analyses: state.analyses.map(item => {
+ if (item.key !== analysis) {
+ return item;
+ }
+ return {
+ ...item,
+ events: item.events.map(
+ eventItem => (eventItem.key === event.key ? { ...eventItem, ...event } : eventItem)
+ )
+ };
+ })
+});
+
+export const deleteAnalysis = (analysis: string) => (state: State) => ({
+ analyses: state.analyses.filter(item => item.key !== analysis)
+});
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js b/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
index 6697449dc5d..2dc04fec94c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/Event.js
@@ -20,17 +20,19 @@
// @flow
import React from 'react';
import EventInner from './EventInner';
-import ChangeCustomEventForm from './forms/ChangeCustomEventForm';
-import RemoveCustomEventForm from './forms/RemoveCustomEventForm';
+import ChangeEventForm from './forms/ChangeEventForm';
+import RemoveEventForm from './forms/RemoveEventForm';
import DeleteIcon from './DeleteIcon';
import ChangeIcon from './ChangeIcon';
-import type { Event as EventType } from '../../../store/projectActivity/duck';
+import type { Event as EventType } from '../types';
type Props = {
analysis: string,
+ canAdmin: boolean,
+ changeEvent: (event: string, name: string) => Promise<*>,
+ deleteEvent: (analysis: string, event: string) => Promise<*>,
event: EventType,
- isFirst: boolean,
- canAdmin: boolean
+ isFirst: boolean
};
type State = {
@@ -41,7 +43,6 @@ type State = {
export default class Event extends React.PureComponent {
mounted: boolean;
props: Props;
-
state: State = {
changing: false,
deleting: false
@@ -77,9 +78,10 @@ export default class Event extends React.PureComponent {
render() {
const { event, canAdmin } = this.props;
- const canChange = ['OTHER', 'VERSION'].includes(event.category);
- const canDelete =
- event.category === 'OTHER' || (event.category === 'VERSION' && !this.props.isFirst);
+ const isOther = event.category === 'OTHER';
+ const isVersion = !isOther && event.category === 'VERSION';
+ const canChange = isOther || isVersion;
+ const canDelete = isOther || (isVersion && !this.props.isFirst);
const showActions = canAdmin && (canChange || canDelete);
return (
@@ -99,13 +101,29 @@ export default class Event extends React.PureComponent {
</div>}
{this.state.changing &&
- <ChangeCustomEventForm event={this.props.event} onClose={this.stopChanging} />}
+ <ChangeEventForm
+ changeEventButtonText={
+ 'project_activity.' + (isVersion ? 'change_version' : 'change_custom_event')
+ }
+ changeEvent={this.props.changeEvent}
+ event={this.props.event}
+ onClose={this.stopChanging}
+ />}
{this.state.deleting &&
- <RemoveCustomEventForm
+ <RemoveEventForm
analysis={this.props.analysis}
+ deleteEvent={this.props.deleteEvent}
event={this.props.event}
onClose={this.stopDeleting}
+ removeEventButtonText={
+ 'project_activity.' + (isVersion ? 'remove_version' : 'remove_custom_event')
+ }
+ removeEventQuestion={
+ 'project_activity.' +
+ (isVersion ? 'remove_version' : 'remove_custom_event') +
+ '.question'
+ }
/>}
</div>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js
index 4135b322fda..62bc0e7e229 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/EventInner.js
@@ -20,7 +20,7 @@
// @flow
import React from 'react';
import { TooltipsContainer } from '../../../components/mixins/tooltips-mixin';
-import type { Event as EventType } from '../../../store/projectActivity/duck';
+import type { Event as EventType } from '../types';
import { translate } from '../../../helpers/l10n';
import './Event.css';
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js b/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
index 36a931422a5..27678c0a9ac 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/Events.js
@@ -21,13 +21,15 @@
import React from 'react';
import { sortBy } from 'lodash';
import Event from './Event';
-import type { Event as EventType } from '../../../store/projectActivity/duck';
+import type { Event as EventType } from '../types';
type Props = {
analysis: string,
+ canAdmin: boolean,
+ changeEvent: (event: string, name: string) => Promise<*>,
+ deleteEvent: (analysis: string, event: string) => Promise<*>,
events: Array<EventType>,
- isFirst: boolean,
- canAdmin: boolean
+ isFirst: boolean
};
export default function Events(props: Props) {
@@ -43,11 +45,13 @@ export default function Events(props: Props) {
<div className="project-activity-events">
{sortedEvents.map(event => (
<Event
- key={event.key}
analysis={props.analysis}
+ canAdmin={props.canAdmin}
+ changeEvent={props.changeEvent}
+ deleteEvent={props.deleteEvent}
event={event}
isFirst={props.isFirst}
- canAdmin={props.canAdmin}
+ key={event.key}
/>
))}
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
index 6e95edb3b25..96ab8cb14ad 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.js
@@ -19,27 +19,24 @@
*/
// @flow
import React from 'react';
-import { connect } from 'react-redux';
import { groupBy } from 'lodash';
import moment from 'moment';
import ProjectActivityAnalysis from './ProjectActivityAnalysis';
import FormattedDate from '../../../components/ui/FormattedDate';
-import { getProjectActivity } from '../../../store/rootReducer';
-import { getAnalyses } from '../../../store/projectActivity/duck';
import { translate } from '../../../helpers/l10n';
-import type { Analysis } from '../../../store/projectActivity/duck';
+import type { Analysis } from '../types';
type Props = {
- project: string,
- analyses?: Array<Analysis>,
- canAdmin: boolean
+ addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>,
+ addVersion: (analysis: string, version: string) => Promise<*>,
+ analyses: Array<Analysis>,
+ canAdmin: boolean,
+ changeEvent: (event: string, name: string) => Promise<*>,
+ deleteAnalysis: (analysis: string) => Promise<*>,
+ deleteEvent: (analysis: string, event: string) => Promise<*>
};
-function ProjectActivityAnalysesList(props: Props) {
- if (!props.analyses) {
- return null;
- }
-
+export default function ProjectActivityAnalysesList(props: Props) {
if (props.analyses.length === 0) {
return <div className="note">{translate('no_results')}</div>;
}
@@ -64,11 +61,15 @@ function ProjectActivityAnalysesList(props: Props) {
{byDay[day] != null &&
byDay[day].map(analysis => (
<ProjectActivityAnalysis
- key={analysis.key}
+ addCustomEvent={props.addCustomEvent}
+ addVersion={props.addVersion}
analysis={analysis}
- isFirst={analysis === firstAnalysis}
- project={props.project}
canAdmin={props.canAdmin}
+ changeEvent={props.changeEvent}
+ deleteAnalysis={props.deleteAnalysis}
+ deleteEvent={props.deleteEvent}
+ isFirst={analysis === firstAnalysis}
+ key={analysis.key}
/>
))}
</ul>
@@ -78,9 +79,3 @@ function ProjectActivityAnalysesList(props: Props) {
</div>
);
}
-
-const mapStateToProps = (state, ownProps) => ({
- analyses: getAnalyses(getProjectActivity(state), ownProps.project)
-});
-
-export default connect(mapStateToProps)(ProjectActivityAnalysesList);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
index 0774d09abb7..01f933beadc 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.js
@@ -20,17 +20,20 @@
// @flow
import React from 'react';
import Events from './Events';
-import AddVersionForm from './forms/AddVersionForm';
-import AddCustomEventForm from './forms/AddCustomEventForm';
+import AddEventForm from './forms/AddEventForm';
import RemoveAnalysisForm from './forms/RemoveAnalysisForm';
import FormattedDate from '../../../components/ui/FormattedDate';
-import type { Analysis } from '../../../store/projectActivity/duck';
import { translate } from '../../../helpers/l10n';
+import type { Analysis } from '../types';
type Props = {
+ addCustomEvent: (analysis: string, name: string, category?: string) => Promise<*>,
+ addVersion: (analysis: string, version: string) => Promise<*>,
analysis: Analysis,
+ changeEvent: (event: string, name: string) => Promise<*>,
+ deleteAnalysis: (analysis: string) => Promise<*>,
+ deleteEvent: (analysis: string, event: string) => Promise<*>,
isFirst: boolean,
- project: string,
canAdmin: boolean
};
@@ -51,17 +54,25 @@ export default function ProjectActivityAnalysis(props: Props) {
<ul className="dropdown-menu dropdown-menu-right">
{version == null &&
<li>
- <AddVersionForm analysis={props.analysis} />
+ <AddEventForm
+ addEvent={props.addVersion}
+ analysis={props.analysis}
+ addEventButtonText="project_activity.add_version"
+ />
</li>}
<li>
- <AddCustomEventForm analysis={props.analysis} />
+ <AddEventForm
+ addEvent={props.addCustomEvent}
+ analysis={props.analysis}
+ addEventButtonText="project_activity.add_custom_event"
+ />
</li>
</ul>
</div>
{!isFirst &&
<div className="display-inline-block little-spacer-left">
- <RemoveAnalysisForm analysis={props.analysis} project={props.project} />
+ <RemoveAnalysisForm analysis={props.analysis} deleteAnalysis={props.deleteAnalysis} />
</div>}
</div>}
@@ -72,9 +83,11 @@ export default function ProjectActivityAnalysis(props: Props) {
{events.length > 0 &&
<Events
analysis={props.analysis.key}
+ canAdmin={canAdmin}
+ changeEvent={props.changeEvent}
+ deleteEvent={props.deleteEvent}
events={events}
isFirst={props.isFirst}
- canAdmin={canAdmin}
/>}
</li>
);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
index fd9ac67fc94..7289766ece9 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.js
@@ -20,54 +20,151 @@
// @flow
import React from 'react';
import Helmet from 'react-helmet';
-import { connect } from 'react-redux';
import ProjectActivityPageHeader from './ProjectActivityPageHeader';
import ProjectActivityAnalysesList from './ProjectActivityAnalysesList';
import ProjectActivityPageFooter from './ProjectActivityPageFooter';
-import { fetchProjectActivity } from '../actions';
-import { getComponent } from '../../../store/rootReducer';
+import throwGlobalError from '../../../app/utils/throwGlobalError';
+import * as api from '../../../api/projectActivity';
+import * as actions from '../actions';
+import { parseQuery, serializeQuery, serializeUrlQuery } from '../utils';
import { translate } from '../../../helpers/l10n';
import './projectActivity.css';
+import type { Analysis, Query, Paging } from '../types';
+import type { RawQuery } from '../../../helpers/query';
type Props = {
- location: { query: { id: string } },
- fetchProjectActivity: (project: string, filter: ?string) => void,
- project: { configuration?: { showHistory: boolean } }
+ location: { pathname: string, query: RawQuery },
+ project: { configuration?: { showHistory: boolean }, key: string },
+ router: { push: ({ pathname: string, query?: RawQuery }) => void }
};
-type State = {
- filter: ?string
+export type State = {
+ analyses: Array<Analysis>,
+ loading: boolean,
+ paging?: Paging,
+ query: Query
};
-class ProjectActivityApp extends React.PureComponent {
+export default class ProjectActivityApp extends React.PureComponent {
+ mounted: boolean;
props: Props;
+ state: State;
- state: State = {
- filter: null
- };
+ constructor(props: Props) {
+ super(props);
+ this.state = { analyses: [], loading: true, query: parseQuery(props.location.query) };
+ }
componentDidMount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.add('dashboard-page');
+ this.mounted = true;
+ this.handleQueryChange();
+ const elem = document.querySelector('html');
+ elem && elem.classList.add('dashboard-page');
+ }
+
+ componentDidUpdate(prevProps: Props) {
+ if (prevProps.location.query !== this.props.location.query) {
+ this.handleQueryChange();
}
- this.props.fetchProjectActivity(this.props.location.query.id);
}
componentWillUnmount() {
- const html = document.querySelector('html');
- if (html) {
- html.classList.remove('dashboard-page');
+ this.mounted = false;
+ const elem = document.querySelector('html');
+ elem && elem.classList.remove('dashboard-page');
+ }
+
+ fetchActivity = (
+ query: Query,
+ additional?: {}
+ ): Promise<{ analyses: Array<Analysis>, paging: Paging }> => {
+ const parameters = {
+ ...serializeQuery(query),
+ ...additional
+ };
+ return api.getProjectActivity(parameters).catch(throwGlobalError);
+ };
+
+ fetchMoreActivity = () => {
+ const { paging, query } = this.state;
+ if (!paging) {
+ return;
}
+
+ this.setState({ loading: true });
+ this.fetchActivity(query, { p: paging.pageIndex + 1 }).then(({ analyses, paging }) => {
+ if (this.mounted) {
+ this.setState((state: State) => ({
+ analyses: state.analyses ? state.analyses.concat(analyses) : analyses,
+ loading: false,
+ paging
+ }));
+ }
+ });
+ };
+
+ addCustomEvent = (analysis: string, name: string, category?: string): Promise<*> =>
+ api
+ .createEvent(analysis, name, category)
+ .then(
+ ({ analysis, ...event }) =>
+ this.mounted && this.setState(actions.addCustomEvent(analysis, event))
+ )
+ .catch(throwGlobalError);
+
+ addVersion = (analysis: string, version: string): Promise<*> =>
+ this.addCustomEvent(analysis, version, 'VERSION');
+
+ deleteEvent = (analysis: string, event: string): Promise<*> =>
+ api
+ .deleteEvent(event)
+ .then(() => this.mounted && this.setState(actions.deleteEvent(analysis, event)))
+ .catch(throwGlobalError);
+
+ changeEvent = (event: string, name: string): Promise<*> =>
+ api
+ .changeEvent(event, name)
+ .then(
+ ({ analysis, ...event }) =>
+ this.mounted && this.setState(actions.changeEvent(analysis, event))
+ )
+ .catch(throwGlobalError);
+
+ deleteAnalysis = (analysis: string): Promise<*> =>
+ api
+ .deleteAnalysis(analysis)
+ .then(() => this.mounted && this.setState(actions.deleteAnalysis(analysis)))
+ .catch(throwGlobalError);
+
+ handleQueryChange() {
+ const query = parseQuery(this.props.location.query);
+ this.setState({ loading: true, query });
+ this.fetchActivity(query).then(({ analyses, paging }) => {
+ if (this.mounted) {
+ this.setState({
+ analyses,
+ loading: false,
+ paging
+ });
+ }
+ });
}
- handleFilter = (filter: ?string) => {
- this.setState({ filter });
- this.props.fetchProjectActivity(this.props.location.query.id, filter);
+ updateQuery = (newQuery: Query) => {
+ this.props.router.push({
+ pathname: this.props.location.pathname,
+ query: {
+ ...serializeUrlQuery({
+ ...this.state.query,
+ ...newQuery
+ }),
+ id: this.props.project.key
+ }
+ });
};
render() {
- const project = this.props.location.query.id;
+ const { query } = this.state;
const { configuration } = this.props.project;
const canAdmin = configuration ? configuration.showHistory : false;
@@ -75,24 +172,24 @@ class ProjectActivityApp extends React.PureComponent {
<div id="project-activity" className="page page-limited">
<Helmet title={translate('project_activity.page')} />
- <ProjectActivityPageHeader
- project={project}
- filter={this.state.filter}
- changeFilter={this.handleFilter}
- />
+ <ProjectActivityPageHeader category={query.category} updateQuery={this.updateQuery} />
- <ProjectActivityAnalysesList project={project} canAdmin={canAdmin} />
+ <ProjectActivityAnalysesList
+ addCustomEvent={this.addCustomEvent}
+ addVersion={this.addVersion}
+ analyses={this.state.analyses}
+ canAdmin={canAdmin}
+ changeEvent={this.changeEvent}
+ deleteAnalysis={this.deleteAnalysis}
+ deleteEvent={this.deleteEvent}
+ />
- <ProjectActivityPageFooter project={project} />
+ <ProjectActivityPageFooter
+ analyses={this.state.analyses}
+ fetchMoreActivity={this.fetchMoreActivity}
+ paging={this.state.paging}
+ />
</div>
);
}
}
-
-const mapStateToProps = (state, ownProps: Props) => ({
- project: getComponent(state, ownProps.location.query.id)
-});
-
-const mapDispatchToProps = { fetchProjectActivity };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectActivityApp);
diff --git a/server/sonar-web/src/main/js/apps/overview/actions.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
index 892cea5e498..9ed72083692 100644
--- a/server/sonar-web/src/main/js/apps/overview/actions.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAppContainer.js
@@ -18,16 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
-import * as api from '../../api/projectActivity';
-import { receiveProjectActivity } from '../../store/projectActivity/duck';
-import { onFail } from '../../store/rootActions';
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router';
+import ProjectActivityApp from './ProjectActivityApp';
+import { getComponent } from '../../../store/rootReducer';
-const PAGE_SIZE = 5;
+const mapStateToProps = (state, ownProps) => ({
+ project: getComponent(state, ownProps.location.query.id)
+});
-export const fetchRecentProjectActivity = (project: string) => (dispatch: Function) =>
- api
- .getProjectActivity(project, { pageSize: PAGE_SIZE })
- .then(
- ({ analyses, paging }) => dispatch(receiveProjectActivity(project, analyses, paging)),
- onFail(dispatch)
- );
+export default connect(mapStateToProps)(withRouter(ProjectActivityApp));
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js
index 4bf4d21b3ac..77428e38105 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageFooter.js
@@ -19,43 +19,18 @@
*/
// @flow
import React from 'react';
-import { connect } from 'react-redux';
import ListFooter from '../../../components/controls/ListFooter';
-import { getProjectActivity } from '../../../store/rootReducer';
-import { getAnalyses, getPaging } from '../../../store/projectActivity/duck';
-import { fetchMoreProjectActivity } from '../actions';
-import type { Paging } from '../../../store/projectActivity/duck';
+import type { Paging } from '../types';
-class ProjectActivityPageFooter extends React.PureComponent {
- props: {
- analyses: Array<*>,
- paging: ?Paging,
- project: string,
- fetchMoreProjectActivity: (project: string) => void
- };
+type Props = {
+ analyses: Array<*>,
+ fetchMoreActivity: () => void,
+ paging?: Paging
+};
- handleLoadMore = () => {
- this.props.fetchMoreProjectActivity(this.props.project);
- };
-
- render() {
- const { analyses, paging } = this.props;
-
- if (!paging || analyses.length === 0) {
- return null;
- }
-
- return (
- <ListFooter count={analyses.length} total={paging.total} loadMore={this.handleLoadMore} />
- );
+export default function ProjectActivityPageFooter({ analyses, fetchMoreActivity, paging }: Props) {
+ if (!paging || analyses.length === 0) {
+ return null;
}
+ return <ListFooter count={analyses.length} total={paging.total} loadMore={fetchMoreActivity} />;
}
-
-const mapStateToProps = (state, ownProps) => ({
- analyses: getAnalyses(getProjectActivity(state), ownProps.project),
- paging: getPaging(getProjectActivity(state), ownProps.project)
-});
-
-const mapDispatchToProps = { fetchMoreProjectActivity };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ProjectActivityPageFooter);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js
index 624559acd4c..8dd16cb5045 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityPageHeader.js
@@ -21,18 +21,20 @@
import React from 'react';
import Select from 'react-select';
import { translate } from '../../../helpers/l10n';
+import type { RawQuery } from '../../../helpers/query';
type Props = {
- changeFilter: (filter: ?string) => void,
- filter: ?string
+ updateQuery: RawQuery => void,
+ category?: string
};
export default class ProjectActivityPageHeader extends React.PureComponent {
props: Props;
- handleChange = (option: null | { value: string }) => {
- this.props.changeFilter(option && option.value);
+ handleCategoryChange = (option: ?{ value: string }) => {
+ this.props.updateQuery({ category: option ? option.value : '' });
};
+
render() {
const selectOptions = ['VERSION', 'QUALITY_GATE', 'QUALITY_PROFILE', 'OTHER'].map(category => ({
label: translate('event.category', category),
@@ -47,9 +49,9 @@ export default class ProjectActivityPageHeader extends React.PureComponent {
placeholder={translate('filter_verb') + '...'}
clearable={true}
searchable={false}
- value={this.props.filter}
+ value={this.props.category}
options={selectOptions}
- onChange={this.handleChange}
+ onChange={this.handleCategoryChange}
/>
</div>
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js
deleted file mode 100644
index 9ff07425850..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddCustomEventForm.js
+++ /dev/null
@@ -1,40 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import { addCustomEvent } from '../../actions';
-import AddEventForm from './AddEventForm';
-import type { Analysis } from '../../../../store/projectActivity/duck';
-
-type Props = {
- addEvent: (analysis: string, name: string, category?: string) => Promise<*>,
- analysis: Analysis
-};
-
-function AddCustomEventForm(props: Props) {
- return <AddEventForm {...props} addEventButtonText="project_activity.add_custom_event" />;
-}
-
-const mapStateToProps = null;
-
-const mapDispatchToProps = { addEvent: addCustomEvent };
-
-export default connect(mapStateToProps, mapDispatchToProps)(AddCustomEventForm);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
index 759a60313df..d83b4244d6a 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.js
@@ -20,8 +20,8 @@
// @flow
import React from 'react';
import Modal from 'react-modal';
-import type { Analysis } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';
+import type { Analysis } from '../../types';
type Props = {
addEvent: (analysis: string, name: string, category?: string) => Promise<*>,
@@ -38,7 +38,6 @@ type State = {
export default class AddEventForm extends React.PureComponent {
mounted: boolean;
props: Props;
-
state: State = {
open: false,
processing: false,
@@ -131,7 +130,6 @@ export default class AddEventForm extends React.PureComponent {
</div>}
</footer>
</form>
-
</Modal>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js
deleted file mode 100644
index 0cbb7d0d6f5..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeCustomEventForm.js
+++ /dev/null
@@ -1,41 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import ChangeEventForm from './ChangeEventForm';
-import { changeEvent } from '../../actions';
-import type { Event } from '../../../../store/projectActivity/duck';
-
-type Props = {
- changeEvent: (event: string, name: string) => Promise<*>,
- event: Event,
- onClose: () => void
-};
-
-const ChangeCustomEventForm = (props: Props) => (
- <ChangeEventForm {...props} changeEventButtonText="project_activity.change_custom_event" />
-);
-
-const mapStateToProps = null;
-
-const mapDispatchToProps = { changeEvent };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChangeCustomEventForm);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js
index 240ad559c06..6a091dcae76 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.js
@@ -20,8 +20,8 @@
// @flow
import React from 'react';
import Modal from 'react-modal';
-import type { Event } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';
+import type { Event } from '../../types';
type Props = {
changeEvent: (event: string, name: string) => Promise<*>,
@@ -129,7 +129,6 @@ export default class ChangeEventForm extends React.PureComponent {
</div>}
</footer>
</form>
-
</Modal>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
index ec3676d3ed8..9b0e215c686 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.js
@@ -19,16 +19,13 @@
*/
// @flow
import React from 'react';
-import { connect } from 'react-redux';
import Modal from 'react-modal';
-import type { Analysis } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';
-import { deleteAnalysis } from '../../actions';
+import type { Analysis } from '../../types';
type Props = {
analysis: Analysis,
- deleteAnalysis: (project: string, analysis: string) => Promise<*>,
- project: string
+ deleteAnalysis: (analysis: string) => Promise<*>
};
type State = {
@@ -36,10 +33,9 @@ type State = {
processing: boolean
};
-class RemoveAnalysisForm extends React.PureComponent {
+export default class RemoveAnalysisForm extends React.PureComponent {
mounted: boolean;
props: Props;
-
state: State = {
open: false,
processing: false
@@ -81,7 +77,7 @@ class RemoveAnalysisForm extends React.PureComponent {
e.preventDefault();
this.setState({ processing: true });
this.props
- .deleteAnalysis(this.props.project, this.props.analysis.key)
+ .deleteAnalysis(this.props.analysis.key)
.then(this.stopProcessingAndClose, this.stopProcessing);
};
@@ -128,9 +124,3 @@ class RemoveAnalysisForm extends React.PureComponent {
);
}
}
-
-const mapStateToProps = null;
-
-const mapDispatchToProps = { deleteAnalysis };
-
-export default connect(mapStateToProps, mapDispatchToProps)(RemoveAnalysisForm);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js
deleted file mode 100644
index 6223957e45a..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveCustomEventForm.js
+++ /dev/null
@@ -1,48 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import RemoveEventForm from './RemoveEventForm';
-import { deleteEvent } from '../../actions';
-import type { Event } from '../../../../store/projectActivity/duck';
-
-type Props = {
- analysis: string,
- event: Event,
- deleteEvent: (analysis: string, event: string) => Promise<*>,
- onClose: () => void
-};
-
-function RemoveCustomEventForm(props: Props) {
- return (
- <RemoveEventForm
- {...props}
- removeEventButtonText="project_activity.remove_custom_event"
- removeEventQuestion="project_activity.remove_custom_event.question"
- />
- );
-}
-
-const mapStateToProps = null;
-
-const mapDispatchToProps = { deleteEvent };
-
-export default connect(mapStateToProps, mapDispatchToProps)(RemoveCustomEventForm);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js
index e17ed059e85..b23522db21c 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.js
@@ -20,8 +20,8 @@
// @flow
import React from 'react';
import Modal from 'react-modal';
-import type { Event } from '../../../../store/projectActivity/duck';
import { translate } from '../../../../helpers/l10n';
+import type { Event } from '../../types';
type Props = {
analysis: string,
@@ -39,7 +39,6 @@ type State = {
export default class RemoveEventForm extends React.PureComponent {
mounted: boolean;
props: Props;
-
state: State = {
processing: false
};
@@ -108,7 +107,6 @@ export default class RemoveEventForm extends React.PureComponent {
</div>}
</footer>
</form>
-
</Modal>
);
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js b/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js
deleted file mode 100644
index 3b0459170b8..00000000000
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveVersionForm.js
+++ /dev/null
@@ -1,48 +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.
- */
-// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import RemoveEventForm from './RemoveEventForm';
-import { deleteEvent } from '../../actions';
-import type { Event } from '../../../../store/projectActivity/duck';
-
-type Props = {
- analysis: string,
- event: Event,
- deleteEvent: (analysis: string, event: string) => Promise<*>,
- onClose: () => void
-};
-
-function RemoveVersionForm(props: Props) {
- return (
- <RemoveEventForm
- {...props}
- removeEventButtonText="project_activity.remove_version"
- removeEventQuestion="project_activity.remove_version.question"
- />
- );
-}
-
-const mapStateToProps = null;
-
-const mapDispatchToProps = { deleteEvent };
-
-export default connect(mapStateToProps, mapDispatchToProps)(RemoveVersionForm);
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/routes.js b/server/sonar-web/src/main/js/apps/projectActivity/routes.js
index 3c5a57d10ec..e11d7fbe8b4 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/routes.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/routes.js
@@ -21,7 +21,7 @@ const routes = [
{
getIndexRoute(_, callback) {
require.ensure([], require =>
- callback(null, { component: require('./components/ProjectActivityApp').default })
+ callback(null, { component: require('./components/ProjectActivityAppContainer').default })
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddVersionForm.js b/server/sonar-web/src/main/js/apps/projectActivity/types.js
index a415a6ec9e6..b3d8211dfc8 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddVersionForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/types.js
@@ -18,23 +18,27 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import { addVersion } from '../../actions';
-import AddEventForm from './AddEventForm';
-import type { Analysis } from '../../../../store/projectActivity/duck';
-type Props = {
- addEvent: (analysis: string, version: string) => Promise<*>,
- analysis: Analysis
+export type Event = {
+ key: string,
+ name: string,
+ category: string,
+ description?: string
};
-function AddVersionForm(props: Props) {
- return <AddEventForm {...props} addEventButtonText="project_activity.add_version" />;
-}
-
-const mapStateToProps = null;
+export type Analysis = {
+ key: string,
+ date: string,
+ events: Array<Event>
+};
-const mapDispatchToProps = { addEvent: addVersion };
+export type Paging = {
+ pageIndex: number,
+ pageSize: number,
+ total: number
+};
-export default connect(mapStateToProps, mapDispatchToProps)(AddVersionForm);
+export type Query = {
+ project: string,
+ category: string
+};
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeVersionForm.js b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
index 8eb2192cafc..be1646db137 100644
--- a/server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeVersionForm.js
+++ b/server/sonar-web/src/main/js/apps/projectActivity/utils.js
@@ -18,24 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
-import React from 'react';
-import { connect } from 'react-redux';
-import ChangeEventForm from './ChangeEventForm';
-import { changeEvent } from '../../actions';
-import type { Event } from '../../../../store/projectActivity/duck';
+import { cleanQuery, parseAsString, serializeString } from '../../helpers/query';
+import type { Query } from './types';
+import type { RawQuery } from '../../helpers/query';
-type Props = {
- changeEvent: (event: string, name: string) => Promise<*>,
- event: Event,
- onClose: () => void
-};
+export const parseQuery = (urlQuery: RawQuery): Query => ({
+ project: parseAsString(urlQuery['id']),
+ category: parseAsString(urlQuery['category'])
+});
-function ChangeVersionForm(props: Props) {
- return <ChangeEventForm {...props} changeEventButtonText="project_activity.change_version" />;
-}
+export const serializeQuery = (query: Query): Query =>
+ cleanQuery({
+ project: serializeString(query.project),
+ category: serializeString(query.category)
+ });
-const mapStateToProps = null;
-
-const mapDispatchToProps = { changeEvent };
-
-export default connect(mapStateToProps, mapDispatchToProps)(ChangeVersionForm);
+export const serializeUrlQuery = (query: Query): RawQuery =>
+ cleanQuery({
+ id: serializeString(query.project),
+ category: serializeString(query.category)
+ });
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
index 746a6fee39e..13078f320a5 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.js
@@ -28,11 +28,12 @@ import VisualizationsContainer from '../visualizations/VisualizationsContainer';
import { parseUrlQuery } from '../store/utils';
import { translate } from '../../../helpers/l10n';
import * as utils from '../utils';
+import type { RawQuery } from '../../../helpers/query';
import '../styles.css';
type Props = {|
isFavorite: boolean,
- location: { pathname: string, query: { [string]: string } },
+ location: { pathname: string, query: RawQuery },
fetchProjects: (query: string, isFavorite: boolean, organization?: {}) => Promise<*>,
organization?: { key: string },
router: {
@@ -43,7 +44,7 @@ type Props = {|
|};
type State = {
- query: { [string]: string }
+ query: RawQuery
};
export default class AllProjects extends React.PureComponent {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js
index f3e39df8b4d..bab378af29e 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/DefaultPageSelector.js
@@ -25,12 +25,13 @@ import AllProjectsContainer from './AllProjectsContainer';
import { getCurrentUser } from '../../../store/rootReducer';
import { isFavoriteSet, isAllSet } from '../utils';
import { searchProjects } from '../../../api/components';
+import type { RawQuery } from '../../../helpers/query';
type Props = {
currentUser: { isLoggedIn: boolean },
location: { query: {} },
router: {
- replace: (location: { pathname?: string, query?: { [string]: string } }) => void
+ replace: (location: { pathname?: string, query?: RawQuery }) => void
}
};
diff --git a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
index 45ed4b6e8a8..77cd3f97cc8 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/FavoriteFilter.js
@@ -22,13 +22,14 @@ import React from 'react';
import { IndexLink, Link } from 'react-router';
import { translate } from '../../../helpers/l10n';
import { saveAll, saveFavorite } from '../utils';
+import type { RawQuery } from '../../../helpers/query';
type Props = {
user: {
isLoggedIn?: boolean
},
organization?: { key: string },
- query: { [string]: string }
+ query: RawQuery
};
export default class FavoriteFilter extends React.PureComponent {
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
index dc1aa2de123..356690901e6 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageHeader.js
@@ -25,6 +25,7 @@ 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';
type Props = {|
currentUser?: { isLoggedIn: boolean },
@@ -33,7 +34,7 @@ type Props = {|
organization?: { key: string },
projects: Array<*>,
projectsAppState: { loading: boolean, total?: number },
- query: { [string]: string },
+ query: RawQuery,
onSortChange: (sort: string, desc: boolean) => void,
selectedSort: string,
view: string,
diff --git a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
index 9771b00f497..65fa894d22c 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
+++ b/server/sonar-web/src/main/js/apps/projects/components/PageSidebar.js
@@ -37,11 +37,12 @@ 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';
type Props = {
isFavorite: boolean,
organization?: { key: string },
- query: { [string]: string },
+ query: RawQuery,
view: string,
visualization: string
};
diff --git a/server/sonar-web/src/main/js/apps/projects/store/utils.js b/server/sonar-web/src/main/js/apps/projects/store/utils.js
index 4c383fe8d0c..190facb928c 100644
--- a/server/sonar-web/src/main/js/apps/projects/store/utils.js
+++ b/server/sonar-web/src/main/js/apps/projects/store/utils.js
@@ -34,6 +34,7 @@ const getAsLevel = value => {
return null;
};
+// TODO Maybe use parseAsString form helpers/query
const getAsString = value => {
if (!value) {
return null;
@@ -41,6 +42,7 @@ const getAsString = value => {
return value;
};
+// TODO Maybe move it to helpers/query
const getAsArray = (values, elementGetter) => {
if (!values) {
return null;