Просмотр исходного кода

SONARCLOUD-63 Update help dropdown with product news

tags/7.5
Grégoire Aubert 6 лет назад
Родитель
Сommit
1d0b5d8c3e

+ 63
- 0
server/sonar-web/src/main/js/api/news.ts Просмотреть файл

@@ -0,0 +1,63 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* eslint-disable camelcase */
import { getCorsJSON } from '../helpers/request';

interface PrismicRef {
id: string;
ref: string;
}

export interface PrismicNews {
data: { title: string };
last_publication_date: string;
uid: string;
}

const PRISMIC_API_URL = 'https://sonarsource.cdn.prismic.io/api/v2';

export function fetchPrismicRefs() {
return getCorsJSON(PRISMIC_API_URL).then((response: { refs: Array<PrismicRef> }) => {
const master = response && response.refs.find(ref => ref.id === 'master');
if (!master) {
return Promise.reject('No master ref found');
}
return Promise.resolve(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: Array<PrismicNews> }) => results);
}

+ 16
- 5
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx Просмотреть файл

@@ -20,12 +20,13 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import ProductNewsMenuItem from './ProductNewsMenuItem';
import { SuggestionLink } from './SuggestionsProvider';
import { CurrentUser, isLoggedIn } from '../../types';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';
import { DropdownOverlay } from '../../../components/controls/Dropdown';
import { isSonarCloud } from '../../../helpers/system';
import { DropdownOverlay } from '../../../components/controls/Dropdown';

interface Props {
currentUser: CurrentUser;
@@ -87,17 +88,27 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
<React.Fragment>
<li className="divider" />
<li>
<a href="https://community.sonarsource.com/" rel="noopener noreferrer" target="_blank">
<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://about.sonarcloud.io/news/', 'sc-icon.svg', 'Product News')}
{this.renderIconLink('https://twitter.com/sonarcloud', 'twitter-icon.svg', 'Twitter')}
</li>
<li>
{this.renderIconLink('https://twitter.com/sonarcloud', 'twitter-icon.svg', 'Twitter')}
{this.renderIconLink(
'https://blog.sonarsource.com/product/SonarCloud',
'sc-icon.svg',
translate('embed_docs.news')
)}
</li>
<li>
<ProductNewsMenuItem tag="SonarCloud" />
</li>
</React.Fragment>
);
@@ -125,7 +136,7 @@ export default class EmbedDocsPopup extends React.PureComponent<Props> {
{this.renderIconLink(
'https://www.sonarsource.com/resources/product-news/',
'sq-icon.svg',
'Product News'
translate('embed_docs.news')
)}
</li>
<li>

+ 1
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx Просмотреть файл

@@ -34,6 +34,7 @@ interface State {
}

export default class EmbedDocsPopupHelper extends React.PureComponent<Props, State> {
mounted = false;
state: State = { helpOpen: false };

componentDidMount() {

+ 131
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/ProductNewsMenuItem.tsx Просмотреть файл

@@ -0,0 +1,131 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { connect } from 'react-redux';
import { fetchPrismicRefs, fetchPrismicNews, PrismicNews } from '../../../api/news';
import { getGlobalSettingValue } from '../../../store/rootReducer';
import DateFormatter from '../../../components/intl/DateFormatter';
import ChevronRightIcon from '../../../components/icons-components/ChevronRightcon';
import PlaceholderBar from '../../../components/ui/PlaceholderBar';

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>Latest news</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>Latest news</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: any): StateProps => ({
accessToken: (getGlobalSettingValue(state, 'sonar.prismic.accessToken') || {}).value
});

export default connect<StateProps, {}, OwnProps>(mapStateToProps)(ProductNewsMenuItem);

+ 22
- 2
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx Просмотреть файл

@@ -19,14 +19,34 @@
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import EmbedDocsPopups from '../EmbedDocsPopup';
import EmbedDocsPopup from '../EmbedDocsPopup';
import { isSonarCloud } from '../../../../helpers/system';

jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn().mockReturnValue(false) }));

const suggestions = [{ link: '#', text: 'foo' }, { link: '#', text: 'bar' }];

it('should display suggestion links', () => {
const context = {};
const wrapper = shallow(
<EmbedDocsPopups
<EmbedDocsPopup
currentUser={{ isLoggedIn: true }}
onClose={jest.fn()}
suggestions={suggestions}
/>,
{
context
}
);
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('should display correct links for SonarCloud', () => {
(isSonarCloud as jest.Mock<any>).mockReturnValueOnce(true);
const context = {};
const wrapper = shallow(
<EmbedDocsPopup
currentUser={{ isLoggedIn: true }}
onClose={jest.fn()}
suggestions={suggestions}

+ 48
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/ProductNewsMenuItem-test.tsx Просмотреть файл

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { shallow } from 'enzyme';
import { ProductNewsMenuItem } from '../ProductNewsMenuItem';
import { fetchPrismicRefs, fetchPrismicNews } from '../../../../api/news';
import { waitAndUpdate } from '../../../../helpers/testUtils';

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();
});

+ 124
- 2
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap Просмотреть файл

@@ -1,5 +1,127 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should display correct links for SonarCloud 1`] = `
<DropdownOverlay>
<ul
className="menu abs-width-240"
>
<React.Fragment>
<li
className="menu-header"
>
embed_docs.suggestion
</li>
<li
key="0"
>
<Link
onClick={[MockFunction]}
onlyActiveOnIndex={false}
style={Object {}}
to="#"
>
foo
</Link>
</li>
<li
key="1"
>
<Link
onClick={[MockFunction]}
onlyActiveOnIndex={false}
style={Object {}}
to="#"
>
bar
</Link>
</li>
<li
className="divider"
/>
</React.Fragment>
<li>
<Link
onClick={[MockFunction]}
onlyActiveOnIndex={false}
style={Object {}}
to="/documentation"
>
embed_docs.documentation
</Link>
</li>
<li>
<Link
onClick={[MockFunction]}
onlyActiveOnIndex={false}
style={Object {}}
to="/web_api"
>
api_documentation.page
</Link>
</li>
<React.Fragment>
<li
className="divider"
/>
<li>
<a
href="https://community.sonarsource.com/c/help/sc"
rel="noopener noreferrer"
target="_blank"
>
embed_docs.get_help
</a>
</li>
<li
className="divider"
/>
<li
className="menu-header"
>
embed_docs.stay_connected
</li>
<li>
<a
href="https://twitter.com/sonarcloud"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Twitter"
className="spacer-right"
height="18"
src="/images/embed-doc/twitter-icon.svg"
width="18"
/>
Twitter
</a>
</li>
<li>
<a
href="https://blog.sonarsource.com/product/SonarCloud"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="embed_docs.news"
className="spacer-right"
height="18"
src="/images/embed-doc/sc-icon.svg"
width="18"
/>
embed_docs.news
</a>
</li>
<li>
<Connect(ProductNewsMenuItem)
tag="SonarCloud"
/>
</li>
</React.Fragment>
</ul>
</DropdownOverlay>
`;

exports[`should display suggestion links 1`] = `
<DropdownOverlay>
<ul
@@ -95,13 +217,13 @@ exports[`should display suggestion links 1`] = `
target="_blank"
>
<img
alt="Product News"
alt="embed_docs.news"
className="spacer-right"
height="18"
src="/images/embed-doc/sq-icon.svg"
width="18"
/>
Product News
embed_docs.news
</a>
</li>
<li>

+ 95
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/ProductNewsMenuItem-test.tsx.snap Просмотреть файл

@@ -0,0 +1,95 @@
// 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>
Latest news
</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>
Latest news
</h4>
<DateFormatter
date="2018-04-06T12:07:19+0000"
/>
</div>
<p
className="little-spacer-bottom"
>
My Product News
</p>
</div>
<ChevronRightIcon
className="flex-0"
/>
</a>
`;

+ 10
- 0
server/sonar-web/src/main/js/app/styles/components/menu.css Просмотреть файл

@@ -54,6 +54,16 @@
transition: none;
}

.menu > li > a.rich-item {
display: flex;
align-items: center;
border: 1px solid var(--gray80);
border-radius: 4px;
margin: 4px 10px;
padding: 2px 8px;
white-space: normal;
}

.menu .divider {
height: 1px;
margin: 6px 0;

+ 4
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css Просмотреть файл

@@ -310,6 +310,10 @@ td.big-spacer-top {
flex: 1;
}

.flex-0 {
flex: 0 0 auto;
}

.flex-shrink {
flex-shrink: 1;
min-width: 0;

+ 25
- 0
server/sonar-web/src/main/js/components/ui/PlaceholderBar.css Просмотреть файл

@@ -0,0 +1,25 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
.placeholder-bar {
display: inline-block;
vertical-align: middle;
height: 8px;
background-color: currentColor;
}

+ 31
- 0
server/sonar-web/src/main/js/components/ui/PlaceholderBar.tsx Просмотреть файл

@@ -0,0 +1,31 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import './PlaceholderBar.css';

interface Props {
color?: string;
width: number;
height?: number;
}

export default function PlaceholderBar({ color, width, height }: Props) {
return <span className="placeholder-bar" style={{ color, width, height }} />;
}

+ 17
- 0
server/sonar-web/src/main/js/helpers/request.ts Просмотреть файл

@@ -212,6 +212,23 @@ export function getJSON(url: string, data?: RequestData): Promise<any> {
.then(parseJSON);
}

/**
* Shortcut to do a CORS GET request and return responsejson
*/
export function getCorsJSON(url: string, data?: RequestData): Promise<any> {
return corsRequest(url)
.setData(data)
.submit()
.then(response => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response);
} else {
return Promise.reject({ response });
}
})
.then(parseJSON);
}

/**
* Shortcut to do a POST request and return response json
*/

+ 3
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties Просмотреть файл

@@ -2553,11 +2553,12 @@ organization.change_visibility_form.submit=Change Default Visibility
# EMBEDED DOCS
#
#------------------------------------------------------------------------------
embed_docs.suggestion=Suggestions For This Page
embed_docs.analyze_new_project=Analyze New Project
embed_docs.documentation=Documentation
embed_docs.get_help=Get Help
embed_docs.news=Product News
embed_docs.stay_connected=Stay Connected
embed_docs.analyze_new_project=Analyze New Project
embed_docs.suggestion=Suggestions For This Page

#------------------------------------------------------------------------------
#

Загрузка…
Отмена
Сохранить