+++ /dev/null
-/*
- * 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
- }
- }));
-}
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 {
);
}
- 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>
{translate('api_documentation.page')}
</Link>
</li>
- {isSonarCloud() ? this.renderSonarCloudLinks() : this.renderSonarQubeLinks()}
+ <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>
</ul>
</DropdownOverlay>
);
+++ /dev/null
-/*
- * 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);
+++ /dev/null
-/*
- * 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();
-});
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-/*
- * 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>
- </>
- );
- }
-}
+++ /dev/null
-/*
- * 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>
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-// 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>
-`;
+++ /dev/null
-/*
- * 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;
-}