diff options
author | lukasz-jarocki-sonarsource <77498856+lukasz-jarocki-sonarsource@users.noreply.github.com> | 2021-02-26 09:29:39 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-02-26 20:07:39 +0000 |
commit | 9cb17b6dbce261af578b7c5fe430fa340d4ff1ad (patch) | |
tree | decb117810be86812b2849f8adb388de5a97919d /server/sonar-web | |
parent | 26ca7559fab8b6a0379468a0a5b5f7d83c1baaa9 (diff) | |
download | sonarqube-9cb17b6dbce261af578b7c5fe430fa340d4ff1ad.tar.gz sonarqube-9cb17b6dbce261af578b7c5fe430fa340d4ff1ad.zip |
Revert SONAR-14478, SONAR-14462, SONAR-14461
* Revert "SONAR-14478 - Main Branch Documentation"
This reverts commit 59eae7cf3f2e611e162a4e0122ae5846b10a45b1.
* Revert "SONAR-14462 Do not display the branch name until the main branch is analyzed for the first time"
This reverts commit 20f7319c06affdae62d39d1bad002f16504465a2.
* Revert "SONAR-14461 main branch detection"
This reverts commit c04baa1e8e3b492953d66a6bc4111c01f3ee3069.
* Revert "SONAR-14461 Remove hardcoded usage of 'master'"
This reverts commit 32eefaf2d36af375af280cc3ba664fd71e0f6afd.
* Revert "SONAR-14461 save the default main branch when needed"
This reverts commit 879a4be2afc570b2248fb4d639f42f913215805b.
Diffstat (limited to 'server/sonar-web')
21 files changed, 1417 insertions, 250 deletions
diff --git a/server/sonar-web/src/main/js/api/news.ts b/server/sonar-web/src/main/js/api/news.ts new file mode 100644 index 00000000000..3b4e00488d0 --- /dev/null +++ b/server/sonar-web/src/main/js/api/news.ts @@ -0,0 +1,139 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { getCorsJSON } from 'sonar-ui-common/helpers/request'; + +interface PrismicRef { + id: string; + ref: string; +} + +export interface PrismicNews { + data: { title: string }; + last_publication_date: string; + uid: string; +} + +interface PrismicResponse { + page: number; + results: PrismicResult[]; + results_per_page: number; + total_results_size: number; +} + +interface PrismicResult { + data: { + notification: string; + publication_date: string; + body: PrismicResultFeature[]; + }; +} + +interface PrismicResultFeature { + items: Array<{ + category: { + data: { + color: string; + name: string; + }; + }; + }>; + primary: { + description: string; + read_more_link: { + url?: string; + }; + }; +} + +export interface PrismicFeatureNews { + notification: string; + publicationDate: string; + features: Array<{ + categories: Array<{ + color: string; + name: string; + }>; + description: string; + readMore?: string; + }>; +} + +const PRISMIC_API_URL = 'https://sonarsource.cdn.prismic.io/api/v2'; + +export function fetchPrismicRefs() { + return getCorsJSON(PRISMIC_API_URL).then((response: { refs: PrismicRef[] }) => { + const master = response && response.refs.find(ref => ref.id === 'master'); + if (!master) { + return Promise.reject('No master ref found'); + } + return master; + }); +} + +export function fetchPrismicNews(data: { + accessToken: string; + ps?: number; + ref: string; + tag?: string; +}) { + const q = ['[[at(document.type, "blog_sonarsource_post")]]']; + if (data.tag) { + q.push(`[[at(document.tags,["${data.tag}"])]]`); + } + return getCorsJSON(PRISMIC_API_URL + '/documents/search', { + access_token: data.accessToken, + orderings: '[document.first_publication_date desc]', + pageSize: data.ps || 1, + q, + ref: data.ref + }).then(({ results }: { results: PrismicNews[] }) => results); +} + +export function fetchPrismicFeatureNews(data: { + accessToken: string; + p?: number; + ps?: number; + ref: string; +}): Promise<{ news: PrismicFeatureNews[]; paging: T.Paging }> { + return getCorsJSON(PRISMIC_API_URL + '/documents/search', { + access_token: data.accessToken, + fetchLinks: 'sc_category.color,sc_category.name', + orderings: '[my.sc_product_news.publication_date desc]', + page: data.p || 1, + pageSize: data.ps || 1, + q: ['[[at(document.type, "sc_product_news")]]'], + ref: data.ref + }).then(({ page, results, results_per_page, total_results_size }: PrismicResponse) => ({ + news: results.map(result => ({ + notification: result.data.notification, + publicationDate: result.data.publication_date, + features: result.data.body.map(feature => ({ + categories: feature.items.map(item => item.category.data).filter(Boolean), + description: feature.primary.description, + readMore: feature.primary.read_more_link.url + })) + })), + paging: { + pageIndex: page, + pageSize: results_per_page, + total: total_results_size + } + })); +} diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx index ab3f44270e7..d5e145cbeea 100644 --- a/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx @@ -22,6 +22,8 @@ import { Link } from 'react-router'; import { DropdownOverlay } from 'sonar-ui-common/components/controls/Dropdown'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; +import { isSonarCloud } from '../../../helpers/system'; +import ProductNewsMenuItem from './ProductNewsMenuItem'; import { SuggestionsContext } from './SuggestionsContext'; interface Props { @@ -67,6 +69,70 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { ); } + renderSonarCloudLinks() { + return ( + <> + <li className="divider" /> + <li> + <a + href="https://community.sonarsource.com/c/help/sc" + rel="noopener noreferrer" + target="_blank"> + {translate('embed_docs.get_help')} + </a> + </li> + <li className="divider" /> + {this.renderTitle(translate('embed_docs.stay_connected'))} + <li> + {this.renderIconLink( + 'https://twitter.com/sonarcloud', + 'embed-doc/twitter-icon.svg', + 'Twitter' + )} + </li> + <li> + {this.renderIconLink( + 'https://blog.sonarsource.com/product/SonarCloud', + 'sonarcloud-square-logo.svg', + translate('embed_docs.blog') + )} + </li> + <li> + <ProductNewsMenuItem tag="SonarCloud" /> + </li> + </> + ); + } + + renderSonarQubeLinks() { + return ( + <> + <li className="divider" /> + <li> + <a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank"> + {translate('embed_docs.get_help')} + </a> + </li> + <li className="divider" /> + {this.renderTitle(translate('embed_docs.stay_connected'))} + <li> + {this.renderIconLink( + 'https://www.sonarqube.org/whats-new/?referrer=sonarqube', + 'embed-doc/sq-icon.svg', + translate('embed_docs.news') + )} + </li> + <li> + {this.renderIconLink( + 'https://twitter.com/SonarQube', + 'embed-doc/twitter-icon.svg', + 'Twitter' + )} + </li> + </> + ); + } + render() { return ( <DropdownOverlay> @@ -82,28 +148,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> { {translate('api_documentation.page')} </Link> </li> - <li className="divider" /> - <li> - <a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank"> - {translate('embed_docs.get_help')} - </a> - </li> - <li className="divider" /> - {this.renderTitle(translate('embed_docs.stay_connected'))} - <li> - {this.renderIconLink( - 'https://www.sonarqube.org/whats-new/?referrer=sonarqube', - 'embed-doc/sq-icon.svg', - translate('embed_docs.news') - )} - </li> - <li> - {this.renderIconLink( - 'https://twitter.com/SonarQube', - 'embed-doc/twitter-icon.svg', - 'Twitter' - )} - </li> + {isSonarCloud() ? this.renderSonarCloudLinks() : this.renderSonarQubeLinks()} </ul> </DropdownOverlay> ); diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx new file mode 100644 index 00000000000..62ed86eae5f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx @@ -0,0 +1,135 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { connect } from 'react-redux'; +import ChevronRightIcon from 'sonar-ui-common/components/icons/ChevronRightIcon'; +import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { fetchPrismicNews, fetchPrismicRefs, PrismicNews } from '../../../api/news'; +import PlaceholderBar from '../../../components/ui/PlaceholderBar'; +import { getGlobalSettingValue, Store } from '../../../store/rootReducer'; + +interface OwnProps { + tag?: string; +} + +interface StateProps { + accessToken?: string; +} + +type Props = OwnProps & StateProps; + +interface State { + loading: boolean; + news?: PrismicNews; +} + +export class ProductNewsMenuItem extends React.PureComponent<Props, State> { + mounted = false; + state: State = { loading: false }; + + componentDidMount() { + this.mounted = true; + this.fetchProductNews(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchProductNews = () => { + const { accessToken, tag } = this.props; + if (accessToken) { + this.setState({ loading: true }); + fetchPrismicRefs() + .then(({ ref }) => fetchPrismicNews({ accessToken, ref, tag })) + .then( + news => { + if (this.mounted) { + this.setState({ news: news[0], loading: false }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + } + }; + + renderPlaceholder() { + return ( + <a className="rich-item new-loading"> + <div className="flex-1"> + <div className="display-inline-flex-center"> + <h4>{translate('embed_docs.latest_blog')}</h4> + <span className="note spacer-left"> + <PlaceholderBar color="#aaa" width={60} /> + </span> + </div> + <p className="little-spacer-bottom"> + <PlaceholderBar color="#aaa" width={84} /> <PlaceholderBar color="#aaa" width={48} />{' '} + <PlaceholderBar color="#aaa" width={24} /> <PlaceholderBar color="#aaa" width={72} />{' '} + <PlaceholderBar color="#aaa" width={24} /> <PlaceholderBar color="#aaa" width={48} /> + </p> + </div> + <ChevronRightIcon className="flex-0" /> + </a> + ); + } + + render() { + const link = 'https://blog.sonarsource.com/'; + const { loading, news } = this.state; + + if (loading) { + return this.renderPlaceholder(); + } + + if (!news) { + return null; + } + + return ( + <a className="rich-item" href={link + news.uid} rel="noopener noreferrer" target="_blank"> + <div className="flex-1"> + <div className="display-inline-flex-center"> + <h4>{translate('embed_docs.latest_blog')}</h4> + <DateFormatter date={news.last_publication_date}> + {formattedDate => <span className="note spacer-left">{formattedDate}</span>} + </DateFormatter> + </div> + <p className="little-spacer-bottom">{news.data.title}</p> + </div> + <ChevronRightIcon className="flex-0" /> + </a> + ); + } +} + +const mapStateToProps = (state: Store): StateProps => { + const accessToken = getGlobalSettingValue(state, 'sonar.prismic.accessToken'); + return { + accessToken: accessToken && accessToken.value + }; +}; + +export default connect(mapStateToProps)(ProductNewsMenuItem); diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/ProductNewsMenuItem-test.tsx b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/ProductNewsMenuItem-test.tsx new file mode 100644 index 00000000000..a6605fb1d6a --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/ProductNewsMenuItem-test.tsx @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; +import { fetchPrismicNews, fetchPrismicRefs } from '../../../../api/news'; +import { ProductNewsMenuItem } from '../ProductNewsMenuItem'; + +jest.mock('../../../../api/news', () => ({ + fetchPrismicRefs: jest.fn().mockResolvedValue({ id: 'master', ref: 'master-ref' }), + fetchPrismicNews: jest.fn().mockResolvedValue([ + { + data: { title: 'My Product News' }, + last_publication_date: '2018-04-06T12:07:19+0000', + uid: 'my-product-news' + } + ]) +})); + +it('should load the product news', async () => { + const wrapper = shallow(<ProductNewsMenuItem accessToken="token" tag="SonarCloud" />); + expect(wrapper).toMatchSnapshot(); + await waitAndUpdate(wrapper); + expect(fetchPrismicRefs).toHaveBeenCalled(); + expect(fetchPrismicNews).toHaveBeenCalledWith({ + accessToken: 'token', + ref: 'master-ref', + tag: 'SonarCloud' + }); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/ProductNewsMenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/ProductNewsMenuItem-test.tsx.snap new file mode 100644 index 00000000000..2cd910101ba --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/ProductNewsMenuItem-test.tsx.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should load the product news 1`] = ` +<a + className="rich-item new-loading" +> + <div + className="flex-1" + > + <div + className="display-inline-flex-center" + > + <h4> + embed_docs.latest_blog + </h4> + <span + className="note spacer-left" + > + <PlaceholderBar + color="#aaa" + width={60} + /> + </span> + </div> + <p + className="little-spacer-bottom" + > + <PlaceholderBar + color="#aaa" + width={84} + /> + + <PlaceholderBar + color="#aaa" + width={48} + /> + + <PlaceholderBar + color="#aaa" + width={24} + /> + + <PlaceholderBar + color="#aaa" + width={72} + /> + + <PlaceholderBar + color="#aaa" + width={24} + /> + + <PlaceholderBar + color="#aaa" + width={48} + /> + </p> + </div> + <ChevronRightIcon + className="flex-0" + /> +</a> +`; + +exports[`should load the product news 2`] = ` +<a + className="rich-item" + href="https://blog.sonarsource.com/my-product-news" + rel="noopener noreferrer" + target="_blank" +> + <div + className="flex-1" + > + <div + className="display-inline-flex-center" + > + <h4> + embed_docs.latest_blog + </h4> + <DateFormatter + date="2018-04-06T12:07:19+0000" + > + <Component /> + </DateFormatter> + </div> + <p + className="little-spacer-bottom" + > + My Product News + </p> + </div> + <ChevronRightIcon + className="flex-0" + /> +</a> +`; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx index cafece6895c..72729ff47ce 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx @@ -57,11 +57,6 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) { /> ); - // Main branch hasn't been analyzed yet && (CE || (DE+ && only one branch)) - if (!component.analysisDate && (!branchesEnabled || !hasManyBranches)) { - return null; - } - return ( <span className={classNames('big-spacer-left flex-0 branch-like-navigation-toggler-container', { diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx index ff00b6d7624..c0b34d01686 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx @@ -30,23 +30,6 @@ it('should render correctly', () => { expect(wrapper).toMatchSnapshot(); }); -it('should not render', () => { - // CE && main branch not analyzed yet - const wrapper = shallowRender({ - appState: mockAppState({ branchesEnabled: false }), - component: mockComponent({ analysisDate: undefined }) - }); - expect(wrapper.type()).toBeNull(); - - // DE+ && main branch not analyzed yet && no other branches - const wrapper1 = shallowRender({ - appState: mockAppState({ branchesEnabled: true }), - component: mockComponent({ analysisDate: undefined }), - branchLikes: [] - }); - expect(wrapper1.type()).toBeNull(); -}); - it('should render the menu trigger if branches are enabled', () => { const wrapper = shallowRender({ appState: mockAppState({ branchesEnabled: true }) }); expect(wrapper).toMatchSnapshot(); @@ -84,7 +67,7 @@ function shallowRender(props?: Partial<BranchLikeNavigationProps>) { <BranchLikeNavigation appState={mockAppState()} branchLikes={branchLikes} - component={mockComponent({ analysisDate: '2021-01-01 01:01:01' })} + component={mockComponent()} currentBranchLike={branchLikes[0]} {...props} /> diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap index 5e1897ca5c1..9a956e3303c 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap @@ -8,7 +8,6 @@ exports[`should render correctly 1`] = ` branchesEnabled={false} component={ Object { - "analysisDate": "2021-01-01 01:01:01", "breadcrumbs": Array [], "key": "my-project", "name": "MyProject", @@ -118,7 +117,6 @@ exports[`should render the menu trigger if branches are enabled 1`] = ` } component={ Object { - "analysisDate": "2021-01-01 01:01:01", "breadcrumbs": Array [], "key": "my-project", "name": "MyProject", @@ -160,7 +158,6 @@ exports[`should render the menu trigger if branches are enabled 1`] = ` branchesEnabled={true} component={ Object { - "analysisDate": "2021-01-01 01:01:01", "breadcrumbs": Array [], "key": "my-project", "name": "MyProject", diff --git a/server/sonar-web/src/main/js/app/components/notifications/NavLatestNotification.tsx b/server/sonar-web/src/main/js/app/components/notifications/NavLatestNotification.tsx new file mode 100644 index 00000000000..30d49578d50 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/NavLatestNotification.tsx @@ -0,0 +1,93 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 differenceInSeconds from 'date-fns/difference_in_seconds'; +import * as React from 'react'; +import ClearIcon from 'sonar-ui-common/components/icons/ClearIcon'; +import NotificationIcon from 'sonar-ui-common/components/icons/NotificationIcon'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { PrismicFeatureNews } from '../../../api/news'; +import './notifications.css'; + +interface Props { + lastNews: PrismicFeatureNews; + notificationsLastReadDate?: Date; + notificationsOptOut?: boolean; + onClick: () => void; + setCurrentUserSetting: (setting: T.CurrentUserSetting) => void; +} + +export default class NavLatestNotification extends React.PureComponent<Props> { + mounted = false; + + checkHasUnread = () => { + const { notificationsLastReadDate, lastNews } = this.props; + return ( + !notificationsLastReadDate || + differenceInSeconds(parseDate(lastNews.publicationDate), notificationsLastReadDate) > 0 + ); + }; + + handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { + event.preventDefault(); + event.currentTarget.blur(); + this.props.onClick(); + }; + + handleDismiss = (event: React.MouseEvent<HTMLAnchorElement>) => { + event.preventDefault(); + event.stopPropagation(); + + this.props.setCurrentUserSetting({ + key: 'notifications.readDate', + value: Date.now().toString() + }); + }; + + render() { + const { notificationsOptOut, lastNews } = this.props; + const hasUnread = this.checkHasUnread(); + const showNotifications = Boolean(!notificationsOptOut && lastNews && hasUnread); + return ( + <> + {showNotifications && ( + <> + <li className="navbar-latest-notification" onClick={this.props.onClick}> + <div className="navbar-latest-notification-wrapper"> + <span className="badge badge-info">{translate('new')}</span> + <span className="label">{lastNews.notification}</span> + </div> + </li> + <li className="navbar-latest-notification-dismiss"> + <a className="navbar-icon" href="#" onClick={this.handleDismiss}> + <ClearIcon size={12} thin={true} /> + </a> + </li> + </> + )} + <li> + <a className="navbar-icon" href="#" onClick={this.handleClick}> + <NotificationIcon hasUnread={hasUnread && !notificationsOptOut} /> + </a> + </li> + </> + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx new file mode 100644 index 00000000000..92536433eb9 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/NotificationsSidebar.tsx @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 classNames from 'classnames'; +import * as differenceInSeconds from 'date-fns/difference_in_seconds'; +import * as React from 'react'; +import { ClearButton } from 'sonar-ui-common/components/controls/buttons'; +import Modal from 'sonar-ui-common/components/controls/Modal'; +import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; +import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; +import { translate } from 'sonar-ui-common/helpers/l10n'; +import { PrismicFeatureNews } from '../../../api/news'; + +export interface Props { + fetchMoreFeatureNews: () => void; + loading: boolean; + loadingMore: boolean; + news: PrismicFeatureNews[]; + onClose: () => void; + notificationsLastReadDate?: Date; + paging?: T.Paging; +} + +export default function NotificationsSidebar(props: Props) { + const { loading, loadingMore, news, notificationsLastReadDate, paging } = props; + const header = translate('embed_docs.whats_new'); + return ( + <Modal contentLabel={header} onRequestClose={props.onClose}> + <div className="notifications-sidebar"> + <div className="notifications-sidebar-top"> + <h3>{header}</h3> + <ClearButton + className="button-tiny" + iconProps={{ size: 12, thin: true }} + onClick={props.onClose} + /> + </div> + <div className="notifications-sidebar-content"> + {loading ? ( + <div className="text-center"> + <DeferredSpinner className="big-spacer-top" timeout={200} /> + </div> + ) : ( + news.map((slice, index) => ( + <Notification + key={slice.publicationDate} + notification={slice} + unread={isUnread(index, slice.publicationDate, notificationsLastReadDate)} + /> + )) + )} + </div> + {!loading && paging && paging.total > news.length && ( + <div className="notifications-sidebar-footer"> + <div className="spacer-top note text-center"> + <a className="spacer-left" href="#" onClick={props.fetchMoreFeatureNews}> + {translate('show_more')} + </a> + {loadingMore && ( + <DeferredSpinner className="text-bottom spacer-left position-absolute" /> + )} + </div> + </div> + )} + </div> + </Modal> + ); +} + +export function isUnread(index: number, notificationDate: string, lastReadDate?: Date) { + return !lastReadDate ? index < 1 : differenceInSeconds(notificationDate, lastReadDate) > 0; +} + +interface NotificationProps { + notification: PrismicFeatureNews; + unread: boolean; +} + +export function Notification({ notification, unread }: NotificationProps) { + return ( + <div className={classNames('notifications-sidebar-slice', { unread })}> + <h4> + <DateFormatter date={notification.publicationDate} long={false} /> + </h4> + {notification.features.map((feature, index) => ( + <Feature feature={feature} key={index} /> + ))} + </div> + ); +} + +interface FeatureProps { + feature: PrismicFeatureNews['features'][0]; +} + +export function Feature({ feature }: FeatureProps) { + return ( + <div className="feature"> + <ul className="categories spacer-bottom"> + {feature.categories.map(category => ( + <li key={category.name} style={{ backgroundColor: category.color }}> + {category.name} + </li> + ))} + </ul> + <span>{feature.description}</span> + {feature.readMore && ( + <a + className="learn-more" + href={feature.readMore} + rel="noopener noreferrer nofollow" + target="_blank"> + {translate('learn_more')} + </a> + )} + </div> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/notifications/__tests__/NavLatestNotification-test.tsx b/server/sonar-web/src/main/js/app/components/notifications/__tests__/NavLatestNotification-test.tsx new file mode 100644 index 00000000000..334563bdced --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/__tests__/NavLatestNotification-test.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import { PrismicFeatureNews } from '../../../../api/news'; +import NavLatestNotification from '../NavLatestNotification'; + +it('should render correctly if there are new features, and the user has not opted out', () => { + const wrapper = shallowRender(); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.navbar-latest-notification')).toHaveLength(1); +}); + +it('should render correctly if there are new features, but the user has opted out', () => { + const wrapper = shallowRender({ notificationsOptOut: true }); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.navbar-latest-notification')).toHaveLength(0); +}); + +it('should render correctly if there are no new unread features', () => { + const wrapper = shallowRender({ + notificationsLastReadDate: parseDate('2018-12-31T12:07:19+0000') + }); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('.navbar-latest-notification')).toHaveLength(0); +}); + +function shallowRender(props: Partial<NavLatestNotification['props']> = {}) { + const lastNews: PrismicFeatureNews = { + notification: '10 Java rules, Github checks, Security Hotspots, BitBucket branch decoration', + publicationDate: '2018-04-06', + features: [ + { + categories: [{ color: '#ff0000', name: 'Java' }], + description: '10 new Java rules' + } + ] + }; + return shallow( + <NavLatestNotification + lastNews={lastNews} + notificationsLastReadDate={parseDate('2018-01-01T12:07:19+0000')} + notificationsOptOut={false} + onClick={jest.fn()} + setCurrentUserSetting={jest.fn()} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/notifications/__tests__/NotificationsSidebar-test.tsx b/server/sonar-web/src/main/js/app/components/notifications/__tests__/NotificationsSidebar-test.tsx new file mode 100644 index 00000000000..f9b92ec27d0 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/__tests__/NotificationsSidebar-test.tsx @@ -0,0 +1,119 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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 { shallow } from 'enzyme'; +import * as React from 'react'; +import { parseDate } from 'sonar-ui-common/helpers/dates'; +import NotificationsSidebar, { + Feature, + isUnread, + Notification, + Props +} from '../NotificationsSidebar'; + +const news: Props['news'] = [ + { + notification: '10 Java rules, Github checks, Security Hotspots, BitBucket branch decoration', + publicationDate: '2018-04-06', + features: [ + { + categories: [ + { color: '#ff0000', name: 'Java' }, + { color: '#00ff00', name: 'Rules' } + ], + description: '10 new Java rules' + }, + { + categories: [{ color: '#0000ff', name: 'BitBucket' }], + description: 'BitBucket branch decoration', + readMore: 'http://example.com' + } + ] + }, + { + notification: 'Some other notification', + publicationDate: '2018-04-05', + features: [ + { + categories: [{ color: '#0000ff', name: 'BitBucket' }], + description: 'BitBucket branch decoration', + readMore: 'http://example.com' + } + ] + } +]; + +describe('#NotificationSidebar', () => { + it('should render correctly if there are new features', () => { + const wrapper = shallowRender({ loading: true }); + expect(wrapper).toMatchSnapshot(); + wrapper.setProps({ loading: false }); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.find('Notification')).toHaveLength(2); + }); + + it('should render correctly if there are no new unread features', () => { + const wrapper = shallowRender({ + notificationsLastReadDate: parseDate('2018-12-31') + }); + expect(wrapper.find('Notification')).toHaveLength(2); + expect(wrapper.find('Notification[unread=true]')).toHaveLength(0); + }); +}); + +describe('#isUnread', () => { + it('should be unread', () => { + expect(isUnread(0, '2018-12-14', undefined)).toBe(true); + expect(isUnread(1, '2018-12-14', parseDate('2018-12-12'))).toBe(true); + }); + + it('should be read', () => { + expect(isUnread(0, '2018-12-16', parseDate('2018-12-16'))).toBe(false); + expect(isUnread(1, '2018-12-15', undefined)).toBe(false); + }); +}); + +describe('#Notification', () => { + it('should render correctly', () => { + expect(shallow(<Notification notification={news[1]} unread={false} />)).toMatchSnapshot(); + expect(shallow(<Notification notification={news[1]} unread={true} />)).toMatchSnapshot(); + }); +}); + +describe('#Feature', () => { + it('should render correctly', () => { + expect(shallow(<Feature feature={news[1].features[0]} />)).toMatchSnapshot(); + expect(shallow(<Feature feature={news[0].features[0]} />)).toMatchSnapshot(); + }); +}); + +function shallowRender(props: Partial<Props> = {}) { + return shallow( + <NotificationsSidebar + fetchMoreFeatureNews={jest.fn()} + loading={false} + loadingMore={false} + news={news} + notificationsLastReadDate={parseDate('2018-01-01')} + onClose={jest.fn()} + paging={{ pageIndex: 1, pageSize: 10, total: 20 }} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NavLatestNotification-test.tsx.snap b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NavLatestNotification-test.tsx.snap new file mode 100644 index 00000000000..c748dbfcf16 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NavLatestNotification-test.tsx.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly if there are new features, and the user has not opted out 1`] = ` +<Fragment> + <li + className="navbar-latest-notification" + onClick={[MockFunction]} + > + <div + className="navbar-latest-notification-wrapper" + > + <span + className="badge badge-info" + > + new + </span> + <span + className="label" + > + 10 Java rules, Github checks, Security Hotspots, BitBucket branch decoration + </span> + </div> + </li> + <li + className="navbar-latest-notification-dismiss" + > + <a + className="navbar-icon" + href="#" + onClick={[Function]} + > + <ClearIcon + size={12} + thin={true} + /> + </a> + </li> + <li> + <a + className="navbar-icon" + href="#" + onClick={[Function]} + > + <NotificationIcon + hasUnread={true} + /> + </a> + </li> +</Fragment> +`; + +exports[`should render correctly if there are new features, but the user has opted out 1`] = ` +<Fragment> + <li> + <a + className="navbar-icon" + href="#" + onClick={[Function]} + > + <NotificationIcon + hasUnread={false} + /> + </a> + </li> +</Fragment> +`; + +exports[`should render correctly if there are no new unread features 1`] = ` +<Fragment> + <li> + <a + className="navbar-icon" + href="#" + onClick={[Function]} + > + <NotificationIcon + hasUnread={false} + /> + </a> + </li> +</Fragment> +`; diff --git a/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap new file mode 100644 index 00000000000..4936ee19ea8 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/__tests__/__snapshots__/NotificationsSidebar-test.tsx.snap @@ -0,0 +1,269 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`#Feature should render correctly 1`] = ` +<div + className="feature" +> + <ul + className="categories spacer-bottom" + > + <li + key="BitBucket" + style={ + Object { + "backgroundColor": "#0000ff", + } + } + > + BitBucket + </li> + </ul> + <span> + BitBucket branch decoration + </span> + <a + className="learn-more" + href="http://example.com" + rel="noopener noreferrer nofollow" + target="_blank" + > + learn_more + </a> +</div> +`; + +exports[`#Feature should render correctly 2`] = ` +<div + className="feature" +> + <ul + className="categories spacer-bottom" + > + <li + key="Java" + style={ + Object { + "backgroundColor": "#ff0000", + } + } + > + Java + </li> + <li + key="Rules" + style={ + Object { + "backgroundColor": "#00ff00", + } + } + > + Rules + </li> + </ul> + <span> + 10 new Java rules + </span> +</div> +`; + +exports[`#Notification should render correctly 1`] = ` +<div + className="notifications-sidebar-slice" +> + <h4> + <DateFormatter + date="2018-04-05" + long={false} + /> + </h4> + <Feature + feature={ + Object { + "categories": Array [ + Object { + "color": "#0000ff", + "name": "BitBucket", + }, + ], + "description": "BitBucket branch decoration", + "readMore": "http://example.com", + } + } + key="0" + /> +</div> +`; + +exports[`#Notification should render correctly 2`] = ` +<div + className="notifications-sidebar-slice unread" +> + <h4> + <DateFormatter + date="2018-04-05" + long={false} + /> + </h4> + <Feature + feature={ + Object { + "categories": Array [ + Object { + "color": "#0000ff", + "name": "BitBucket", + }, + ], + "description": "BitBucket branch decoration", + "readMore": "http://example.com", + } + } + key="0" + /> +</div> +`; + +exports[`#NotificationSidebar should render correctly if there are new features 1`] = ` +<Modal + contentLabel="embed_docs.whats_new" + onRequestClose={[MockFunction]} +> + <div + className="notifications-sidebar" + > + <div + className="notifications-sidebar-top" + > + <h3> + embed_docs.whats_new + </h3> + <ClearButton + className="button-tiny" + iconProps={ + Object { + "size": 12, + "thin": true, + } + } + onClick={[MockFunction]} + /> + </div> + <div + className="notifications-sidebar-content" + > + <div + className="text-center" + > + <DeferredSpinner + className="big-spacer-top" + timeout={200} + /> + </div> + </div> + </div> +</Modal> +`; + +exports[`#NotificationSidebar should render correctly if there are new features 2`] = ` +<Modal + contentLabel="embed_docs.whats_new" + onRequestClose={[MockFunction]} +> + <div + className="notifications-sidebar" + > + <div + className="notifications-sidebar-top" + > + <h3> + embed_docs.whats_new + </h3> + <ClearButton + className="button-tiny" + iconProps={ + Object { + "size": 12, + "thin": true, + } + } + onClick={[MockFunction]} + /> + </div> + <div + className="notifications-sidebar-content" + > + <Notification + key="2018-04-06" + notification={ + Object { + "features": Array [ + Object { + "categories": Array [ + Object { + "color": "#ff0000", + "name": "Java", + }, + Object { + "color": "#00ff00", + "name": "Rules", + }, + ], + "description": "10 new Java rules", + }, + Object { + "categories": Array [ + Object { + "color": "#0000ff", + "name": "BitBucket", + }, + ], + "description": "BitBucket branch decoration", + "readMore": "http://example.com", + }, + ], + "notification": "10 Java rules, Github checks, Security Hotspots, BitBucket branch decoration", + "publicationDate": "2018-04-06", + } + } + unread={true} + /> + <Notification + key="2018-04-05" + notification={ + Object { + "features": Array [ + Object { + "categories": Array [ + Object { + "color": "#0000ff", + "name": "BitBucket", + }, + ], + "description": "BitBucket branch decoration", + "readMore": "http://example.com", + }, + ], + "notification": "Some other notification", + "publicationDate": "2018-04-05", + } + } + unread={true} + /> + </div> + <div + className="notifications-sidebar-footer" + > + <div + className="spacer-top note text-center" + > + <a + className="spacer-left" + href="#" + onClick={[MockFunction]} + > + show_more + </a> + </div> + </div> + </div> +</Modal> +`; diff --git a/server/sonar-web/src/main/js/app/components/notifications/notifications.css b/server/sonar-web/src/main/js/app/components/notifications/notifications.css new file mode 100644 index 00000000000..ef18020bf4f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/notifications/notifications.css @@ -0,0 +1,157 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 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. + */ +.navbar-latest-notification { + flex: 0 1 240px; + text-align: right; + overflow: hidden; +} + +.navbar-latest-notification-wrapper { + position: relative; + display: inline-block; + padding: var(--gridSize); + padding-left: 50px; + height: 28px; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + vertical-align: middle; + font-size: var(--smallFontSize); + text-overflow: ellipsis; + white-space: nowrap; + color: var(--sonarcloudBlack500); + background-color: #000; + border-radius: 3px 0 0 3px; + cursor: pointer; +} + +.navbar-latest-notification-wrapper:hover { + color: var(--sonarcloudBlack300); +} + +.navbar-latest-notification-wrapper .badge-info { + position: absolute; + margin-right: var(--gridSize); + left: 6px; + top: 6px; +} + +.navbar-latest-notification-wrapper .label { + display: block; + max-width: 330px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.navbar-latest-notification-dismiss .navbar-icon { + height: 28px; + background-color: #000; + border-radius: 0 3px 3px 0; + padding: var(--gridSize) 7px !important; + margin-left: 1px; + margin-right: var(--gridSize); + color: var(--sonarcloudBlack500) !important; +} + +.navbar-latest-notification-dismiss .navbar-icon:hover { + color: var(--sonarcloudBlack300) !important; +} + +.notifications-sidebar { + position: fixed; + top: 0; + right: 0; + bottom: 0; + width: 400px; + display: flex; + flex-direction: column; + background: var(--sonarcloudBlack200); +} + +.notifications-sidebar-top { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: calc(2 * var(--gridSize)); + border-bottom: 1px solid var(--sonarcloudBlack250); + background-color: var(--sonarcloudBlack100); +} + +.notifications-sidebar-top h3 { + font-weight: normal; + font-size: var(--bigFontSize); +} + +.notifications-sidebar-content { + flex: 1 1; + overflow-y: auto; +} + +.notifications-sidebar-footer { + padding-top: var(--gridSize); + border-top: 1px solid var(--sonarcloudBlack250); + flex: 0 0 40px; +} + +.notifications-sidebar-slice h4 { + padding: calc(2 * var(--gridSize)); + padding-bottom: calc(var(--gridSize) / 2); + background-color: var(--sonarcloudBlack200); + font-weight: normal; + font-size: var(--smallFontSize); + text-align: right; + color: var(--sonarcloudBlack500); +} + +.notifications-sidebar-slice .feature:last-of-type { + border-bottom: 1px solid var(--sonarcloudBlack250); +} + +.notifications-sidebar-slice .feature { + padding: calc(2 * var(--gridSize)); + background-color: var(--sonarcloudBlack100); + border-top: 1px solid var(--sonarcloudBlack250); + overflow: hidden; +} + +.notifications-sidebar-slice.unread .feature { + background-color: #e6f6ff; + border-color: #cee4f2; +} + +.notifications-sidebar-slice .learn-more { + clear: both; + float: right; + margin-top: var(--gridSize); +} + +.notifications-sidebar-slice .categories li { + display: inline-block; + padding: 4px; + margin-right: var(--gridSize); + font-size: 9px; + line-height: 8px; + text-transform: uppercase; + font-weight: bold; + color: #fff; + border-radius: 3px; +} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx index ed9efc5b521..32a1c54100d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx @@ -34,7 +34,6 @@ import BranchList from './BranchList'; import ProjectBaselineSelector from './ProjectBaselineSelector'; interface Props { - branchLike: Branch; branchLikes: BranchLike[]; branchesEnabled?: boolean; canAdmin?: boolean; @@ -121,15 +120,13 @@ export default class App extends React.PureComponent<Props, State> { } fetchLeakPeriodSetting() { - const { branchLike, branchesEnabled, component } = this.props; - this.setState({ loading: true }); Promise.all([ getNewCodePeriod(), getNewCodePeriod({ - branch: branchesEnabled ? undefined : branchLike.name, - project: component.key + branch: !this.props.branchesEnabled ? 'master' : undefined, + project: this.props.component.key }) ]).then( ([generalSetting, setting]) => { @@ -229,7 +226,7 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const { branchesEnabled, canAdmin, component, branchLike } = this.props; + const { branchesEnabled, canAdmin, component } = this.props; const { analysis, branchList, @@ -259,7 +256,6 @@ export default class App extends React.PureComponent<Props, State> { {generalSetting && overrideGeneralSetting !== undefined && ( <ProjectBaselineSelector analysis={analysis} - branch={branchLike} branchList={branchList} branchesEnabled={branchesEnabled} component={component.key} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx index 0083374fb70..2b63146fa7f 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx @@ -27,7 +27,7 @@ import BranchAnalysisListRenderer from './BranchAnalysisListRenderer'; interface Props { analysis: string; - branch?: string; + branch: string; component: string; onSelectAnalysis: (analysis: T.ParsedAnalysis) => void; } diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx index 4d133b25ee1..574ce1a048d 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/ProjectBaselineSelector.tsx @@ -34,7 +34,6 @@ import BranchAnalysisList from './BranchAnalysisList'; export interface ProjectBaselineSelectorProps { analysis?: string; - branch: Branch; branchList: Branch[]; branchesEnabled?: boolean; component: string; @@ -83,7 +82,6 @@ function branchToOption(b: Branch) { export default function ProjectBaselineSelector(props: ProjectBaselineSelectorProps) { const { analysis, - branch, branchList, branchesEnabled, component, @@ -165,7 +163,7 @@ export default function ProjectBaselineSelector(props: ProjectBaselineSelectorPr {selected === 'SPECIFIC_ANALYSIS' && ( <BranchAnalysisList analysis={analysis || ''} - branch={branch.name} + branch="master" component={component} onSelectAnalysis={props.onSelectAnalysis} /> diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx index 65cd8e22f08..f4c3fd6bc94 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/App-test.tsx @@ -35,14 +35,8 @@ jest.mock('../../../../api/newCodePeriod', () => ({ setNewCodePeriod: jest.fn().mockResolvedValue({}) })); -it('should render correctly', async () => { - let wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - - wrapper = shallowRender({ branchesEnabled: false }); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot('without branch support'); +it('should render correctly', () => { + expect(shallowRender()).toMatchSnapshot(); }); it('should initialize correctly', async () => { @@ -106,7 +100,6 @@ it('should handle errors gracefully', async () => { function shallowRender(props: Partial<App['props']> = {}) { return shallow<App>( <App - branchLike={mockBranch()} branchLikes={[mockMainBranch()]} branchesEnabled={true} canAdmin={true} diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx index 294b63939ee..0e1cf612c5a 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/ProjectBaselineSelector-test.tsx @@ -19,7 +19,7 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; +import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; import ProjectBaselineSelector, { ProjectBaselineSelectorProps } from '../ProjectBaselineSelector'; it('should render correctly', () => { @@ -105,7 +105,6 @@ it('should disable the save button when date is invalid', () => { function shallowRender(props: Partial<ProjectBaselineSelectorProps> = {}) { return shallow( <ProjectBaselineSelector - branch={mockBranch()} branchList={[mockMainBranch()]} branchesEnabled={true} component="" diff --git a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap index e642a0c6573..7b76b1ebb90 100644 --- a/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectBaseline/components/__tests__/__snapshots__/App-test.tsx.snap @@ -11,186 +11,7 @@ exports[`should render correctly 1`] = ` <AppHeader canAdmin={true} /> - <div - className="panel-white project-baseline" - > - <h2> - project_baseline.default_setting - </h2> - <ProjectBaselineSelector - analysis="" - branch={ - Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": false, - "name": "branch-6.7", - } - } - branchList={ - Array [ - Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": true, - "name": "master", - }, - ] - } - branchesEnabled={true} - component="my-project" - currentSetting="PREVIOUS_VERSION" - days="30" - generalSetting={ - Object { - "type": "PREVIOUS_VERSION", - } - } - onCancel={[Function]} - onSelectAnalysis={[Function]} - onSelectDays={[Function]} - onSelectReferenceBranch={[Function]} - onSelectSetting={[Function]} - onSubmit={[Function]} - onToggleSpecificSetting={[Function]} - overrideGeneralSetting={true} - referenceBranch="master" - saving={false} - selected="PREVIOUS_VERSION" - /> - <div - className="spacer-top invisible" - > - <span - className="text-success" - > - <AlertSuccessIcon - className="spacer-right" - /> - settings.state.saved - </span> - </div> - <div - className="huge-spacer-top branch-baseline-selector" - > - <hr /> - <h2> - project_baseline.configure_branches - </h2> - <BranchList - branchList={ - Array [ - Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": true, - "name": "master", - }, - ] - } - component={ - Object { - "breadcrumbs": Array [], - "key": "my-project", - "name": "MyProject", - "qualifier": "TRK", - "qualityGate": Object { - "isDefault": true, - "key": "30", - "name": "Sonar way", - }, - "qualityProfiles": Array [ - Object { - "deleted": false, - "key": "my-qp", - "language": "ts", - "name": "Sonar way", - }, - ], - "tags": Array [], - } - } - inheritedSetting={ - Object { - "type": "PREVIOUS_VERSION", - "value": undefined, - } - } - /> - </div> - </div> - </div> -</Fragment> -`; - -exports[`should render correctly: without branch support 1`] = ` -<Fragment> - <Suggestions - suggestions="project_baseline" - /> - <div - className="page page-limited" - > - <AppHeader - canAdmin={true} - /> - <div - className="panel-white project-baseline" - > - <ProjectBaselineSelector - analysis="" - branch={ - Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": false, - "name": "branch-6.7", - } - } - branchList={ - Array [ - Object { - "analysisDate": "2018-01-01", - "excludedFromPurge": true, - "isMain": true, - "name": "master", - }, - ] - } - branchesEnabled={false} - component="my-project" - currentSetting="PREVIOUS_VERSION" - days="30" - generalSetting={ - Object { - "type": "PREVIOUS_VERSION", - } - } - onCancel={[Function]} - onSelectAnalysis={[Function]} - onSelectDays={[Function]} - onSelectReferenceBranch={[Function]} - onSelectSetting={[Function]} - onSubmit={[Function]} - onToggleSpecificSetting={[Function]} - overrideGeneralSetting={true} - referenceBranch="master" - saving={false} - selected="PREVIOUS_VERSION" - /> - <div - className="spacer-top invisible" - > - <span - className="text-success" - > - <AlertSuccessIcon - className="spacer-right" - /> - settings.state.saved - </span> - </div> - </div> + <DeferredSpinner /> </div> </Fragment> `; |