From af00018774baa18f3539560de991e747cba74848 Mon Sep 17 00:00:00 2001 From: Stas Vilchik Date: Wed, 22 Aug 2018 13:06:28 +0200 Subject: [PATCH] drop favorites from redux store (#631) --- .../sonar-web/src/main/js/api/components.ts | 5 +- .../components/extensions/exposeLibraries.ts | 4 +- .../components/MeasureFavoriteContainer.js | 103 ----------- .../components/MeasureFavoriteContainer.tsx | 86 +++++++++ .../__tests__/__snapshots__/App-test.tsx.snap | 2 +- .../components/SourceViewer/SourceViewer.tsx | 34 +--- .../SourceViewer/SourceViewerBase.tsx | 171 ++++++++---------- .../SourceViewer/SourceViewerHeader.tsx | 9 +- .../js/components/controls/FavoriteBase.tsx | 26 ++- .../controls/FavoriteBaseStateless.tsx | 51 ------ .../components/controls/FavoriteContainer.ts | 58 ------ .../__tests__/FavoriteBaseStateless-test.tsx | 53 ------ .../FavoriteBaseStateless-test.tsx.snap | 13 -- .../WorkspaceComponentViewer-test.tsx.snap | 2 +- .../src/main/js/store/favorites/duck.ts | 81 --------- .../src/main/js/store/rootReducer.js | 5 - 16 files changed, 195 insertions(+), 508 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js create mode 100644 server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx delete mode 100644 server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx delete mode 100644 server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts delete mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx delete mode 100644 server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/store/favorites/duck.ts diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index 899e6a1c450..671fb298cbf 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -26,7 +26,8 @@ import { MyProject, Metric, ComponentMeasure, - LightComponent + LightComponent, + SourceViewerFile } from '../app/types'; export interface BaseSearchProjectsParameters { @@ -287,7 +288,7 @@ export function getSuggestions( export function getComponentForSourceViewer( data: { component: string } & BranchParameters -): Promise { +): Promise { return getJSON('/api/components/app', data); } diff --git a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts index e86d1d372d2..36abc33bb09 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts @@ -27,7 +27,7 @@ import * as request from '../../../helpers/request'; import DateFromNow from '../../../components/intl/DateFromNow'; import DateFormatter from '../../../components/intl/DateFormatter'; import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; -import FavoriteContainer from '../../../components/controls/FavoriteContainer'; +import Favorite from '../../../components/controls/Favorite'; import HomePageSelect from '../../../components/controls/HomePageSelect'; import ListFooter from '../../../components/controls/ListFooter'; import Modal from '../../../components/controls/Modal'; @@ -86,7 +86,7 @@ const exposeLibraries = () => { DropdownIcon, DuplicationsRating, EditButton, - FavoriteContainer, + Favorite, HelpIcon, HelpTooltip, HomePageSelect, diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js deleted file mode 100644 index 627913abc1c..00000000000 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import React from 'react'; -import { connect } from 'react-redux'; -import FavoriteContainer from '../../../components/controls/FavoriteContainer'; -import { getComponentForSourceViewer } from '../../../api/components'; -import { receiveFavorites } from '../../../store/favorites/duck'; -import { isMainBranch } from '../../../helpers/branches'; - -/*:: type FavComponent = { key: string, canMarkAsFavorite: boolean, fav: boolean }; */ - -/*:: type Props = { - branchLike?: { id?: string; name: string }, - className?: string, - component: string, - onReceiveComponent: (component: FavComponent) => void -}; */ - -/*:: type State = { component: ?FavComponent }; */ - -class MeasureFavoriteContainer extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - state /*: State */ = { - component: null - }; - - componentDidMount() { - this.mounted = true; - this.fetchComponentFavorite(this.props); - } - - componentWillReceiveProps(nextProps /*: Props */) { - if (nextProps.component !== this.props.component) { - this.fetchComponentFavorite(nextProps); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchComponentFavorite({ component, onReceiveComponent } /*: Props */) { - getComponentForSourceViewer({ component }).then(component => { - this.setState({ component }); - onReceiveComponent(component); - }); - } - - render() { - const { component } = this.state; - if ( - component == null || - !component.canMarkAsFavorite || - (this.props.branchLike && !isMainBranch(this.props.branchLike)) - ) { - return null; - } - return ( - - ); - } -} - -const mapStateToProps = null; - -const mapDispatchToProps = { - onReceiveComponent: (component /*: FavComponent */) => dispatch => { - if (component.canMarkAsFavorite) { - const favorites = []; - const notFavorites = []; - if (component.fav) { - favorites.push({ key: component.key }); - } else { - notFavorites.push({ key: component.key }); - } - dispatch(receiveFavorites(favorites, notFavorites)); - } - } -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(MeasureFavoriteContainer); diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx new file mode 100644 index 00000000000..d062ad6af50 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureFavoriteContainer.tsx @@ -0,0 +1,86 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import Favorite from '../../../components/controls/Favorite'; +import { getComponentForSourceViewer } from '../../../api/components'; +import { isMainBranch } from '../../../helpers/branches'; +import { BranchLike, SourceViewerFile } from '../../../app/types'; + +type FavComponent = Pick; + +interface Props { + branchLike?: BranchLike; + className?: string; + component: string; +} + +interface State { + component?: FavComponent; +} + +export default class MeasureFavoriteContainer extends React.PureComponent { + mounted = false; + state: State = {}; + + componentDidMount() { + this.mounted = true; + this.fetchComponentFavorite(); + } + + componentDidUpdate(prevProps: Props) { + if (prevProps.component !== this.props.component) { + this.fetchComponentFavorite(); + } + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchComponentFavorite() { + getComponentForSourceViewer({ component: this.props.component }).then( + component => { + if (this.mounted) { + this.setState({ component }); + } + }, + () => {} + ); + } + + render() { + const { component } = this.state; + if ( + !component || + !component.canMarkAsFavorite || + (this.props.branchLike && !isMainBranch(this.props.branchLike)) + ) { + return null; + } + return ( + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap index c513d6b7a6d..80a0c87199c 100644 --- a/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/component/components/__tests__/__snapshots__/App-test.tsx.snap @@ -4,7 +4,7 @@ exports[`renders 1`] = `
- void; -} - -const onReceiveComponent = (component: SourceViewerFile) => (dispatch: Dispatch) => { - if (component.canMarkAsFavorite) { - const favorites = []; - const notFavorites = []; - if (component.fav) { - favorites.push({ key: component.key }); - } else { - notFavorites.push({ key: component.key }); - } - dispatch(receiveFavorites(favorites, notFavorites)); - } -}; - -const mapDispatchToProps: DispatchProps = { onReceiveComponent }; - -type OwnProps = Omit; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(lazyLoad(() => import(/* webpackPrefetch: true */ './SourceViewerBase'))); +const SourceViewer = lazyLoad(() => import(/* webpackPrefetch: true */ './SourceViewerBase')); +export default SourceViewer; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx index c403c76d948..6ea9df12ea0 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerBase.tsx @@ -67,17 +67,17 @@ export interface Props { // but kept to maintaint the location indexes highlightedLocations?: (FlowLocation | undefined)[]; highlightedLocationMessage?: { index: number; text: string | undefined }; - loadComponent?: ( + loadComponent: ( component: string, branchLike: BranchLike | undefined ) => Promise; - loadIssues?: ( + loadIssues: ( component: string, from: number, to: number, branchLike: BranchLike | undefined ) => Promise; - loadSources?: ( + loadSources: ( component: string, from: number, to: number, @@ -88,7 +88,6 @@ export interface Props { onIssueChange?: (issue: Issue) => void; onIssueSelect?: (issueKey: string) => void; onIssueUnselect?: () => void; - onReceiveComponent: (component: SourceViewerFile) => void; scroll?: (element: HTMLElement) => void; selectedIssue?: string; } @@ -128,7 +127,10 @@ export default class SourceViewerBase extends React.PureComponent displayAllIssues: false, displayIssueLocationsCount: true, displayIssueLocationsLink: true, - displayLocationMarkers: true + displayLocationMarkers: true, + loadComponent: defaultLoadComponent, + loadIssues: defaultLoadIssues, + loadSources: defaultLoadSources }; constructor(props: Props) { @@ -204,21 +206,6 @@ export default class SourceViewerBase extends React.PureComponent this.mounted = false; } - // react typings do not take `defaultProps` into account, - // so use these getters to get type-safe methods - - get safeLoadComponent() { - return this.props.loadComponent || defaultLoadComponent; - } - - get safeLoadIssues() { - return this.props.loadIssues || defaultLoadIssues; - } - - get safeLoadSources() { - return this.props.loadSources || defaultLoadSources; - } - computeCoverageStatus(lines: SourceLine[]) { return lines.map(line => ({ ...line, coverageStatus: getCoverageStatus(line) })); } @@ -237,7 +224,7 @@ export default class SourceViewerBase extends React.PureComponent fetchComponent() { this.setState({ loading: true }); const loadIssues = (component: SourceViewerFile, sources: SourceLine[]) => { - this.safeLoadIssues(this.props.component, 1, LINES, this.props.branchLike).then( + this.props.loadIssues(this.props.component, 1, LINES, this.props.branchLike).then( issues => { if (this.mounted) { const finalSources = sources.slice(0, LINES); @@ -301,7 +288,6 @@ export default class SourceViewerBase extends React.PureComponent }; const onResolve = (component: SourceViewerFile) => { - this.props.onReceiveComponent(component); const sourcesRequest = component.q === 'FIL' || component.q === 'UTS' ? this.loadSources() : Promise.resolve([]); sourcesRequest.then( @@ -310,10 +296,9 @@ export default class SourceViewerBase extends React.PureComponent ); }; - this.safeLoadComponent(this.props.component, this.props.branchLike).then( - onResolve, - onFailLoadComponent - ); + this.props + .loadComponent(this.props.component, this.props.branchLike) + .then(onResolve, onFailLoadComponent); } fetchSources() { @@ -346,25 +331,27 @@ export default class SourceViewerBase extends React.PureComponent } const firstSourceLine = this.state.sources[0]; const lastSourceLine = this.state.sources[this.state.sources.length - 1]; - this.safeLoadIssues( - this.props.component, - firstSourceLine && firstSourceLine.line, - lastSourceLine && lastSourceLine.line, - this.props.branchLike - ).then( - issues => { - if (this.mounted) { - this.setState({ - issues, - issuesByLine: issuesByLine(issues), - issueLocationsByLine: locationsByLine(issues) - }); + this.props + .loadIssues( + this.props.component, + firstSourceLine && firstSourceLine.line, + lastSourceLine && lastSourceLine.line, + this.props.branchLike + ) + .then( + issues => { + if (this.mounted) { + this.setState({ + issues, + issuesByLine: issuesByLine(issues), + issueLocationsByLine: locationsByLine(issues) + }); + } + }, + () => { + // TODO } - }, - () => { - // TODO - } - ); + ); } loadSources = (): Promise => { @@ -390,10 +377,9 @@ export default class SourceViewerBase extends React.PureComponent // request one additional line to define `hasSourcesAfter` to++; - return this.safeLoadSources(this.props.component, from, to, this.props.branchLike).then( - sources => resolve(sources), - onFailLoadSources - ); + return this.props + .loadSources(this.props.component, from, to, this.props.branchLike) + .then(sources => resolve(sources), onFailLoadSources); }); }; @@ -404,46 +390,43 @@ export default class SourceViewerBase extends React.PureComponent const firstSourceLine = this.state.sources[0]; this.setState({ loadingSourcesBefore: true }); const from = Math.max(1, firstSourceLine.line - LINES); - this.safeLoadSources( - this.props.component, - from, - firstSourceLine.line - 1, - this.props.branchLike - ).then( - sources => { - this.safeLoadIssues( - this.props.component, - from, - firstSourceLine.line - 1, - this.props.branchLike - ).then( - issues => { - if (this.mounted) { - this.setState(prevState => { - const nextIssues = uniqBy( - [...issues, ...(prevState.issues || [])], - issue => issue.key - ); - return { - issues: nextIssues, - issuesByLine: issuesByLine(nextIssues), - issueLocationsByLine: locationsByLine(nextIssues), - loadingSourcesBefore: false, - sources: [...this.computeCoverageStatus(sources), ...(prevState.sources || [])], - symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) } - }; - }); - } - }, - () => { - // TODO - } - ); - }, - () => { - // TODO - } - ); + this.props + .loadSources(this.props.component, from, firstSourceLine.line - 1, this.props.branchLike) + .then( + sources => { + this.props + .loadIssues(this.props.component, from, firstSourceLine.line - 1, this.props.branchLike) + .then( + issues => { + if (this.mounted) { + this.setState(prevState => { + const nextIssues = uniqBy( + [...issues, ...(prevState.issues || [])], + issue => issue.key + ); + return { + issues: nextIssues, + issuesByLine: issuesByLine(nextIssues), + issueLocationsByLine: locationsByLine(nextIssues), + loadingSourcesBefore: false, + sources: [ + ...this.computeCoverageStatus(sources), + ...(prevState.sources || []) + ], + symbolsByLine: { ...prevState.symbolsByLine, ...symbolsByLine(sources) } + }; + }); + } + }, + () => { + // TODO + } + ); + }, + () => { + // TODO + } + ); }; loadSourcesAfter = () => { @@ -455,9 +438,9 @@ export default class SourceViewerBase extends React.PureComponent const fromLine = lastSourceLine.line + 1; // request one additional line to define `hasSourcesAfter` const toLine = lastSourceLine.line + LINES + 1; - this.safeLoadSources(this.props.component, fromLine, toLine, this.props.branchLike).then( + this.props.loadSources(this.props.component, fromLine, toLine, this.props.branchLike).then( sources => { - this.safeLoadIssues(this.props.component, fromLine, toLine, this.props.branchLike).then( + this.props.loadIssues(this.props.component, fromLine, toLine, this.props.branchLike).then( issues => { if (this.mounted) { this.setState(prevState => { @@ -749,13 +732,13 @@ export default class SourceViewerBase extends React.PureComponent } } -function defaultLoadComponent(key: string, branchLike: BranchLike | undefined) { +function defaultLoadComponent(component: string, branchLike: BranchLike | undefined) { return Promise.all([ - getComponentForSourceViewer({ component: key, ...getBranchLikeQuery(branchLike) }), - getComponentData({ component: key, ...getBranchLikeQuery(branchLike) }) + getComponentForSourceViewer({ component, ...getBranchLikeQuery(branchLike) }), + getComponentData({ component, ...getBranchLikeQuery(branchLike) }) ]).then(([component, data]) => ({ ...component, - leakPeriodDate: data.leakPeriodDate && parseDate(data.leakPeriodDate) + leakPeriodDate: data.leakPeriodDate })); } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index 80ce4ba53d2..36595d01856 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -25,7 +25,7 @@ import MeasuresOverlay from './components/MeasuresOverlay'; import { SourceViewerFile, BranchLike } from '../../app/types'; import QualifierIcon from '../icons-components/QualifierIcon'; import Dropdown from '../controls/Dropdown'; -import FavoriteContainer from '../controls/FavoriteContainer'; +import Favorite from '../controls/Favorite'; import ListIcon from '../icons-components/ListIcon'; import { ButtonIcon } from '../ui/buttons'; import { PopupPlacement } from '../ui/popups'; @@ -122,7 +122,12 @@ export default class SourceViewerHeader extends React.PureComponent{fileFromPath(path)} {this.props.sourceViewerFile.canMarkAsFavorite && (!this.props.branchLike || isMainBranch(this.props.branchLike)) && ( - + )}
diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx b/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx index a1bd2cc6866..4dbd3b05cea 100644 --- a/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx +++ b/server/sonar-web/src/main/js/components/controls/FavoriteBase.tsx @@ -67,19 +67,25 @@ export default class FavoriteBase extends React.PureComponent { }; addFavorite() { - this.props.addFavorite().then(() => { - if (this.mounted) { - this.setState({ favorite: true }); - } - }); + this.props.addFavorite().then( + () => { + if (this.mounted) { + this.setState({ favorite: true }); + } + }, + () => {} + ); } removeFavorite() { - this.props.removeFavorite().then(() => { - if (this.mounted) { - this.setState({ favorite: false }); - } - }); + this.props.removeFavorite().then( + () => { + if (this.mounted) { + this.setState({ favorite: false }); + } + }, + () => {} + ); } render() { diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx b/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx deleted file mode 100644 index 544056d50b6..00000000000 --- a/server/sonar-web/src/main/js/components/controls/FavoriteBaseStateless.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import * as classNames from 'classnames'; -import FavoriteIcon from '../icons-components/FavoriteIcon'; - -interface Props { - addFavorite: () => void; - className?: string; - favorite: boolean; - removeFavorite: () => void; -} - -export default class FavoriteBaseStateless extends React.PureComponent { - toggleFavorite = (event: React.SyntheticEvent) => { - event.preventDefault(); - if (this.props.favorite) { - this.props.removeFavorite(); - } else { - this.props.addFavorite(); - } - }; - - render() { - return ( - - - - ); - } -} diff --git a/server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts b/server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts deleted file mode 100644 index 8d9da47f1a3..00000000000 --- a/server/sonar-web/src/main/js/components/controls/FavoriteContainer.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { connect } from 'react-redux'; -import FavoriteBaseStateless from './FavoriteBaseStateless'; -import { isFavorite } from '../../store/rootReducer'; -import * as actionCreators from '../../store/favorites/duck'; -import * as api from '../../api/favorites'; -import { addGlobalErrorMessage } from '../../store/globalMessages/duck'; -import { parseError } from '../../helpers/request'; - -const addFavorite = (componentKey: string) => (dispatch: Function) => { - // optimistic update - dispatch(actionCreators.addFavorite(componentKey)); - api.addFavorite(componentKey).catch(error => { - dispatch(actionCreators.removeFavorite(componentKey)); - parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); - }); -}; - -const removeFavorite = (componentKey: string) => (dispatch: Function) => { - // optimistic update - dispatch(actionCreators.removeFavorite(componentKey)); - api.removeFavorite(componentKey).catch(error => { - dispatch(actionCreators.addFavorite(componentKey)); - parseError(error).then(message => dispatch(addGlobalErrorMessage(message))); - }); -}; - -const mapStateToProps = (state: any, ownProps: any) => ({ - favorite: isFavorite(state, ownProps.componentKey) -}); - -const mapDispatchToProps = (dispatch: Function, ownProps: any) => ({ - addFavorite: () => dispatch(addFavorite(ownProps.componentKey)), - removeFavorite: () => dispatch(removeFavorite(ownProps.componentKey)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(FavoriteBaseStateless); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx deleted file mode 100644 index 2ed0bd154a2..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/FavoriteBaseStateless-test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { shallow } from 'enzyme'; -import FavoriteBaseStateless from '../FavoriteBaseStateless'; -import { click } from '../../../helpers/testUtils'; - -it('renders', () => { - expect( - shallow( - - ) - ).toMatchSnapshot(); -}); - -it('adds favorite', () => { - const addFavorite = jest.fn(); - const wrapper = shallow( - - ); - click(wrapper); - expect(addFavorite).toBeCalled(); -}); - -it('removes favorite', () => { - const removeFavorite = jest.fn(); - const wrapper = shallow( - - ); - click(wrapper); - expect(removeFavorite).toBeCalled(); -}); diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap deleted file mode 100644 index 9edbec0cb0f..00000000000 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/FavoriteBaseStateless-test.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` - - - -`; diff --git a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap index 7453ffec082..fb3201016c1 100644 --- a/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/workspace/__tests__/__snapshots__/WorkspaceComponentViewer-test.tsx.snap @@ -28,7 +28,7 @@ exports[`should render 1`] = ` } } > - diff --git a/server/sonar-web/src/main/js/store/favorites/duck.ts b/server/sonar-web/src/main/js/store/favorites/duck.ts deleted file mode 100644 index 710151e4eb2..00000000000 --- a/server/sonar-web/src/main/js/store/favorites/duck.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { uniq, without } from 'lodash'; - -interface Favorite { - key: string; -} - -interface ReceiveFavoritesAction { - type: 'RECEIVE_FAVORITES'; - favorites: Array; - notFavorites: Array; -} - -interface AddFavoriteAction { - type: 'ADD_FAVORITE'; - componentKey: string; -} - -interface RemoveFavoriteAction { - type: 'REMOVE_FAVORITE'; - componentKey: string; -} - -type Action = ReceiveFavoritesAction | AddFavoriteAction | RemoveFavoriteAction; - -type State = string[]; - -export function receiveFavorites( - favorites: Favorite[], - notFavorites: Favorite[] = [] -): ReceiveFavoritesAction { - return { type: 'RECEIVE_FAVORITES', favorites, notFavorites }; -} - -export function addFavorite(componentKey: string): AddFavoriteAction { - return { type: 'ADD_FAVORITE', componentKey }; -} - -export function removeFavorite(componentKey: string): RemoveFavoriteAction { - return { type: 'REMOVE_FAVORITE', componentKey }; -} - -export default function(state: State = [], action: Action): State { - if (action.type === 'RECEIVE_FAVORITES') { - const toAdd = action.favorites.map(f => f.key); - const toRemove = action.notFavorites.map(f => f.key); - return without(uniq([...state, ...toAdd]), ...toRemove); - } - - if (action.type === 'ADD_FAVORITE') { - return uniq([...state, action.componentKey]); - } - - if (action.type === 'REMOVE_FAVORITE') { - return without(state, action.componentKey); - } - - return state; -} - -export function isFavorite(state: State, componentKey: string) { - return state.includes(componentKey); -} diff --git a/server/sonar-web/src/main/js/store/rootReducer.js b/server/sonar-web/src/main/js/store/rootReducer.js index 73c0dc340a1..21e8c828500 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.js +++ b/server/sonar-web/src/main/js/store/rootReducer.js @@ -20,7 +20,6 @@ import { combineReducers } from 'redux'; import appState from './appState/duck'; import users, * as fromUsers from './users/reducer'; -import favorites, * as fromFavorites from './favorites/duck'; import languages, * as fromLanguages from './languages/reducer'; import metrics, * as fromMetrics from './metrics/reducer'; import organizations, * as fromOrganizations from './organizations/duck'; @@ -33,7 +32,6 @@ import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReduce export default combineReducers({ appState, globalMessages, - favorites, languages, metrics, organizations, @@ -66,9 +64,6 @@ export const getUsersByLogins = (state, logins) => fromUsers.getUsersByLogins(st export const getUsers = state => fromUsers.getUsers(state.users); -export const isFavorite = (state, componentKey) => - fromFavorites.isFavorite(state.favorites, componentKey); - export const getMarketplaceState = state => state.marketplace; export const getMetrics = state => fromMetrics.getMetrics(state.metrics); -- 2.39.5