@@ -1,3 +1,7 @@ | |||
{ | |||
"extends": "sonarqube" | |||
"extends": "sonarqube", | |||
"rules": { | |||
"import/extensions": ["error", "never", { "json": "always" }] | |||
} | |||
} |
@@ -103,7 +103,7 @@ | |||
"style-loader": "0.20.3", | |||
"ts-jest": "22.0.1", | |||
"ts-loader": "4.1.0", | |||
"typescript": "2.8.1", | |||
"typescript": "2.8.3", | |||
"typescript-eslint-parser": "15.0.0", | |||
"webpack": "4.1.1", | |||
"webpack-bundle-analyzer": "2.11.1", |
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M.563 0L.562 10.125H9l3.937 3.375V0H.562zm6.188 2.25a1.13 1.13 0 0 1 1.125 1.125A1.13 1.13 0 0 1 6.751 4.5a1.13 1.13 0 0 1-1.125-1.125A1.13 1.13 0 0 1 6.751 2.25zM3.938 6.917v.396H2.251v-.247c0-.208.395-.808 1.406-.808.18 0 .337.024.477.057-.127.22-.196.433-.196.602zm-.281-1.292a.848.848 0 0 1-.844-.844c0-.463.381-.844.844-.844.463 0 .844.381.844.844a.848.848 0 0 1-.844.844zm5.344 1.687h-4.5v-.395c0-.325.633-1.292 2.25-1.292s2.25.958 2.25 1.292v.395zm0-2.531c0-.463.381-.844.844-.844.463 0 .843.381.843.844 0 .463-.38.844-.843.844a.848.848 0 0 1-.844-.844zm2.25 2.531H9.563v-.395c0-.169-.068-.382-.195-.602.14-.033.297-.057.477-.057 1.011 0 1.406.598 1.406.809v.245z" fill="#4169e1" fill-rule="nonzero"/></svg> |
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M12.625 6.154a3.991 3.991 0 0 0-1.902-1.279v-.046C10.723 2.65 8.93.857 6.75.857c-2.179 0-3.972 1.793-3.972 3.972v.053A3.982 3.982 0 0 0 0 8.671c0 2.179 1.793 3.972 3.972 3.972a3.978 3.978 0 0 0 2.791-1.144 3.97 3.97 0 0 0 2.766 1.122c2.178 0 3.971-1.793 3.971-3.972 0-.905-.31-1.784-.877-2.489l.002-.006zm-3.073 5.489a2.99 2.99 0 0 1-2.973-2.971c0-.275-.225-.5-.5-.5a.501.501 0 0 0-.499.5 3.952 3.952 0 0 0 .56 2.032 2.971 2.971 0 0 1-2.164.936 2.985 2.985 0 0 1-2.97-2.97 2.985 2.985 0 0 1 2.97-2.971c.35 0 .697.062 1.026.183h.012c.114.038.223.09.324.155a.5.5 0 1 0 .65-.759 2.224 2.224 0 0 0-.646-.341 3.974 3.974 0 0 0-1.369-.243h-.192A2.985 2.985 0 0 1 6.75 1.85a2.986 2.986 0 0 1 2.972 2.972c0 .96-.466 1.863-1.249 2.42a.5.5 0 1 0 .58.814 3.983 3.983 0 0 0 1.526-2.184 2.979 2.979 0 0 1 1.941 2.789 2.986 2.986 0 0 1-2.969 2.972l.001.01z" fill="#f3702a" fill-rule="nonzero"/></svg> |
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><g fill-rule="nonzero"><path d="M10.686 12.3V8.683h1.201V13.5H1.052V8.683h1.201V12.3h8.433z" fill="#bcbbbb"/><path d="M3.578 8.34L9.47 9.572l.25-1.185-5.893-1.232-.249 1.185zm.779-2.806l5.456 2.541.499-1.091-5.456-2.557-.499 1.107zm1.512-2.681l4.63 3.85.764-.92-4.63-3.85-.764.92zM8.862 0l-.966.717 3.585 4.833.967-.717L8.862 0zM3.453 11.084H9.47V9.883H3.453v1.201z" fill="#f48023"/></g></svg> |
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M11.939 13.282h-.756C11.183 7.203 6.167 2.256 0 2.256v-.755c6.585 0 11.939 5.286 11.939 11.781zm.522-4.053c-.906-3.816-3.997-7-7.872-8.111l.173-.603c4.09 1.174 7.353 4.538 8.311 8.57l-.612.144zm.581-3.575A11.572 11.572 0 0 0 8.562.648l.261-.43a12.081 12.081 0 0 1 4.677 5.22l-.458.216z" fill="#4e9bcd" fill-rule="nonzero"/></svg> |
@@ -0,0 +1 @@ | |||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M13.5 2.57a5.52 5.52 0 0 1-1.593.428 2.685 2.685 0 0 0 1.216-1.524 5.431 5.431 0 0 1-1.756.668 2.666 2.666 0 0 0-2.022-.874c-.765 0-1.417.27-1.957.809a2.67 2.67 0 0 0-.809 1.958c0 .205.023.417.068.634a7.724 7.724 0 0 1-3.182-.853A7.84 7.84 0 0 1 .942 1.773a2.713 2.713 0 0 0-.377 1.396c0 .474.112.914.334 1.32.223.405.523.733.9.985a2.746 2.746 0 0 1-1.251-.352v.035c0 .668.21 1.254.63 1.76.42.506.949.824 1.589.955a2.846 2.846 0 0 1-.728.094c-.16 0-.334-.014-.523-.042a2.71 2.71 0 0 0 .977 1.366c.474.357 1.01.541 1.61.552a5.417 5.417 0 0 1-3.435 1.182c-.245 0-.468-.011-.668-.034a7.684 7.684 0 0 0 4.249 1.242c.982 0 1.904-.155 2.766-.467.863-.311 1.599-.728 2.21-1.25a8.407 8.407 0 0 0 1.581-1.803c.442-.68.772-1.39.989-2.129A7.86 7.86 0 0 0 12.112 4 5.805 5.805 0 0 0 13.5 2.57z" fill="#4d9bce" fill-rule="nonzero"/></svg> |
@@ -26,9 +26,9 @@ const WebpackDevServer = require('webpack-dev-server'); | |||
const clearConsole = require('react-dev-utils/clearConsole'); | |||
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); | |||
const errorOverlayMiddleware = require('react-error-overlay/middleware'); | |||
const getMessages = require('./utils/getMessages'); | |||
const getConfig = require('../config/webpack.config'); | |||
const paths = require('../config/paths'); | |||
const getMessages = require('./utils/getMessages'); | |||
const config = getConfig({ production: false }); | |||
@@ -107,7 +107,6 @@ function runDevServer(compiler, host, port, protocol) { | |||
proxy: { | |||
'/api': proxy, | |||
'/fonts': proxy, | |||
'/images': proxy, | |||
'/static': proxy, | |||
'/integration': proxy | |||
} |
@@ -22,6 +22,7 @@ import * as PropTypes from 'prop-types'; | |||
import GlobalNav from './nav/global/GlobalNav'; | |||
import GlobalFooterContainer from './GlobalFooterContainer'; | |||
import GlobalMessagesContainer from './GlobalMessagesContainer'; | |||
import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider'; | |||
import Workspace from '../../components/workspace/Workspace'; | |||
interface Props { | |||
@@ -59,23 +60,28 @@ export default class GlobalContainer extends React.PureComponent<Props, State> { | |||
// it is important to pass `location` down to `GlobalNav` to trigger render on url change | |||
return ( | |||
<div className="global-container"> | |||
<div className="page-wrapper" id="container"> | |||
<div className="page-container"> | |||
<Workspace> | |||
<GlobalNav | |||
closeOnboardingTutorial={this.closeOnboardingTutorial} | |||
isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen} | |||
location={this.props.location} | |||
openOnboardingTutorial={this.openOnboardingTutorial} | |||
/> | |||
<GlobalMessagesContainer /> | |||
{this.props.children} | |||
</Workspace> | |||
<SuggestionsProvider> | |||
{({ suggestions }) => ( | |||
<div className="global-container"> | |||
<div className="page-wrapper" id="container"> | |||
<div className="page-container"> | |||
<Workspace> | |||
<GlobalNav | |||
closeOnboardingTutorial={this.closeOnboardingTutorial} | |||
isOnboardingTutorialOpen={this.state.isOnboardingTutorialOpen} | |||
location={this.props.location} | |||
openOnboardingTutorial={this.openOnboardingTutorial} | |||
suggestions={suggestions} | |||
/> | |||
<GlobalMessagesContainer /> | |||
{this.props.children} | |||
</Workspace> | |||
</div> | |||
</div> | |||
<GlobalFooterContainer /> | |||
</div> | |||
</div> | |||
<GlobalFooterContainer /> | |||
</div> | |||
)} | |||
</SuggestionsProvider> | |||
); | |||
} | |||
} |
@@ -0,0 +1,159 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { Link } from 'react-router'; | |||
import { SuggestionLink } from './SuggestionsProvider'; | |||
import BubblePopup, { BubblePopupPosition } from '../../../components/common/BubblePopup'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
onClose: () => void; | |||
popupPosition?: BubblePopupPosition; | |||
suggestions: Array<SuggestionLink>; | |||
} | |||
export default class EmbedDocsPopup extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
onSonarCloud: PropTypes.bool, | |||
openOnboardingTutorial: PropTypes.func | |||
}; | |||
onAnalyzeProjectClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.context.openOnboardingTutorial(); | |||
}; | |||
renderTitle(text: string) { | |||
return <li className="dropdown-header">{text}</li>; | |||
} | |||
renderSuggestions() { | |||
if (this.props.suggestions.length === 0) { | |||
return null; | |||
} | |||
return ( | |||
<> | |||
{this.renderTitle(translate('embed_docs.suggestion'))} | |||
{this.props.suggestions.map((suggestion, index) => ( | |||
<li key={index}> | |||
<a href={suggestion.link} target="_blank"> | |||
{suggestion.text} <i className="icon-detach" /> | |||
</a> | |||
</li> | |||
))} | |||
<li className="divider" /> | |||
</> | |||
); | |||
} | |||
renderIconLink(link: string, icon: string, text: string) { | |||
return ( | |||
<a href={link} rel="noopener noreferrer" target="_blank"> | |||
<img | |||
alt={text} | |||
className="spacer-right" | |||
height="18" | |||
src={'/images/embed-doc/' + icon} | |||
width="18" | |||
/> | |||
{text} | |||
</a> | |||
); | |||
} | |||
renderSonarCloudLinks() { | |||
return ( | |||
<React.Fragment> | |||
<li className="divider" /> | |||
{this.renderTitle(translate('embed_docs.get_support'))} | |||
<li> | |||
{this.renderIconLink( | |||
'https://about.sonarcloud.io/contact/', | |||
'sc-icon.svg', | |||
translate('embed_docs.contact_form') | |||
)} | |||
</li> | |||
{this.renderTitle(translate('embed_docs.stay_connected'))} | |||
<li> | |||
{this.renderIconLink('https://about.sonarcloud.io/news/', 'sc-icon.svg', 'Product News')} | |||
</li> | |||
<li> | |||
{this.renderIconLink('https://twitter.com/sonarcloud', 'twitter-icon.svg', 'Twitter')} | |||
</li> | |||
</React.Fragment> | |||
); | |||
} | |||
renderSonarQubeLinks() { | |||
return ( | |||
<React.Fragment> | |||
<li> | |||
<a href="#" onClick={this.onAnalyzeProjectClick}> | |||
{translate('embed_docs.analyze_new_project')} | |||
</a> | |||
</li> | |||
<li className="divider" /> | |||
{this.renderTitle(translate('embed_docs.get_support'))} | |||
<li> | |||
{this.renderIconLink( | |||
'https://groups.google.com/forum/#!forum/sonarqube', | |||
'google-group-icon.svg', | |||
'Google Groups' | |||
)} | |||
</li> | |||
<li> | |||
{this.renderIconLink( | |||
'http://stackoverflow.com/questions/tagged/sonarqube', | |||
'so-icon.svg', | |||
'Stack Overflow' | |||
)} | |||
</li> | |||
{this.renderTitle(translate('embed_docs.stay_connected'))} | |||
<li> | |||
{this.renderIconLink('https://blog.sonarsource.com/', 'sq-icon.svg', 'Product News')} | |||
</li> | |||
<li> | |||
{this.renderIconLink('https://twitter.com/SonarQube', 'twitter-icon.svg', 'Twitter')} | |||
</li> | |||
</React.Fragment> | |||
); | |||
} | |||
render() { | |||
return ( | |||
<BubblePopup | |||
customClass="bubble-popup-bottom bubble-popup-menu abs-width-240 embed-docs-popup" | |||
position={this.props.popupPosition}> | |||
<ul className="menu"> | |||
{this.renderSuggestions()} | |||
<li> | |||
<Link target="_blank" to="/documentation"> | |||
{translate('embed_docs.documentation_index')} <i className="icon-detach" /> | |||
</Link> | |||
</li> | |||
{this.context.onSonarCloud && this.renderSonarCloudLinks()} | |||
{!this.context.onSonarCloud && this.renderSonarQubeLinks()} | |||
</ul> | |||
</BubblePopup> | |||
); | |||
} | |||
} |
@@ -0,0 +1,95 @@ | |||
/* | |||
* 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 EmbedDocsPopup from './EmbedDocsPopup'; | |||
import { SuggestionLink } from './SuggestionsProvider'; | |||
import BubblePopupHelper from '../../../components/common/BubblePopupHelper'; | |||
import HelpIcon from '../../../components/icons-components/HelpIcon'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
showTooltip: boolean; | |||
suggestions: Array<SuggestionLink>; | |||
tooltip: boolean; | |||
} | |||
interface State { | |||
helpOpen: boolean; | |||
} | |||
export default class EmbedDocsPopupHelper extends React.PureComponent<Props, State> { | |||
state: State = { helpOpen: false }; | |||
componentDidMount() { | |||
window.addEventListener('keypress', this.onKeyPress); | |||
} | |||
componentWillUnmount() { | |||
window.removeEventListener('keypress', this.onKeyPress); | |||
} | |||
onKeyPress = (event: KeyboardEvent) => { | |||
const { tagName } = event.target as HTMLElement; | |||
const code = event.keyCode || event.which; | |||
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; | |||
const isTriggerKey = code === 63; | |||
if (!isInput && isTriggerKey) { | |||
this.toggleHelp(); | |||
} | |||
}; | |||
setHelpDisplay = (helpOpen: boolean) => { | |||
this.setState({ helpOpen }); | |||
}; | |||
handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.toggleHelp(); | |||
}; | |||
toggleHelp = () => { | |||
this.setState(state => { | |||
return { helpOpen: !state.helpOpen }; | |||
}); | |||
}; | |||
closeHelp = () => { | |||
this.setState({ helpOpen: false }); | |||
}; | |||
render() { | |||
return ( | |||
<BubblePopupHelper | |||
isOpen={this.state.helpOpen} | |||
offset={{ horizontal: 12, vertical: -10 }} | |||
popup={<EmbedDocsPopup onClose={this.closeHelp} suggestions={this.props.suggestions} />} | |||
position="bottomleft" | |||
togglePopup={this.setHelpDisplay}> | |||
<Tooltip | |||
overlay={this.props.tooltip ? translate('tutorials.follow_later') : undefined} | |||
visible={this.props.showTooltip}> | |||
<a className="navbar-help" href="#" onClick={this.handleClick}> | |||
<HelpIcon /> | |||
</a> | |||
</Tooltip> | |||
</BubblePopupHelper> | |||
); | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
{ | |||
"projects": [ | |||
{ "link": "#", "text": "Foo Suggestion " }, | |||
{ "link": "#", "text": "Bar Suggestion " } | |||
], | |||
"profiles": [ | |||
{ "link": "#", "text": "Foo Suggestion " }, | |||
{ "link": "#", "text": "Bar Suggestion " }, | |||
{ "link": "#", "text": "Baz Suggestion " } | |||
] | |||
} |
@@ -0,0 +1,53 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { SuggestionsContext } from './SuggestionsContext'; | |||
interface Props { | |||
suggestions: string; | |||
} | |||
export default class Suggestions extends React.PureComponent<Props> { | |||
context!: { suggestions: SuggestionsContext }; | |||
static contextTypes = { | |||
suggestions: PropTypes.object.isRequired | |||
}; | |||
componentDidMount() { | |||
this.context.suggestions.addSuggestions(this.props.suggestions); | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
if (prevProps.suggestions !== this.props.suggestions) { | |||
this.context.suggestions.removeSuggestions(this.props.suggestions); | |||
this.context.suggestions.addSuggestions(prevProps.suggestions); | |||
} | |||
} | |||
componentWillUnmount() { | |||
this.context.suggestions.removeSuggestions(this.props.suggestions); | |||
} | |||
render() { | |||
return null; | |||
} | |||
} |
@@ -17,14 +17,7 @@ | |||
* 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 { CurrentUser, AppState } from '../../types'; | |||
export interface Props { | |||
currentUser: CurrentUser; | |||
onClose: () => void; | |||
onSonarCloud?: boolean; | |||
onTutorialSelect: () => void; | |||
export interface SuggestionsContext { | |||
addSuggestions: (key: string) => void; | |||
removeSuggestions: (key: string) => void; | |||
} | |||
export default class GlobalHelp extends React.PureComponent<Props> {} |
@@ -0,0 +1,84 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import * as suggestionsJson from './EmbedDocsSuggestions.json'; | |||
import { SuggestionsContext } from './SuggestionsContext'; | |||
export interface SuggestionLink { | |||
text: string; | |||
link: string; | |||
} | |||
interface SuggestionsJson { | |||
[key: string]: Array<SuggestionLink>; | |||
} | |||
interface Props { | |||
children: ({ suggestions }: { suggestions: Array<SuggestionLink> }) => React.ReactNode; | |||
} | |||
interface State { | |||
suggestions: Array<SuggestionLink>; | |||
} | |||
export default class SuggestionsProvider extends React.Component<Props, State> { | |||
keys: string[] = []; | |||
static childContextTypes = { | |||
suggestions: PropTypes.object | |||
}; | |||
state = { suggestions: [] }; | |||
getChildContext = (): { suggestions: SuggestionsContext } => { | |||
return { | |||
suggestions: { | |||
addSuggestions: this.addSuggestions, | |||
removeSuggestions: this.removeSuggestions | |||
} | |||
}; | |||
}; | |||
fetchSuggestions = () => { | |||
const jsonList = suggestionsJson as SuggestionsJson; | |||
let suggestions: Array<SuggestionLink> = []; | |||
this.keys.forEach(key => { | |||
if (jsonList[key]) { | |||
suggestions = [...jsonList[key], ...suggestions]; | |||
} | |||
}); | |||
this.setState({ suggestions }); | |||
}; | |||
addSuggestions = (newKey: string) => { | |||
this.keys = [...this.keys, newKey]; | |||
this.fetchSuggestions(); | |||
}; | |||
removeSuggestions = (oldKey: string) => { | |||
this.keys = this.keys.filter(key => key !== oldKey); | |||
this.fetchSuggestions(); | |||
}; | |||
render() { | |||
return this.props.children({ suggestions: this.state.suggestions }); | |||
} | |||
} |
@@ -17,26 +17,17 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import EmbedDocsPopups from '../EmbedDocsPopup'; | |||
/*:: | |||
type Props = { onTutorialSelect: () => void }; | |||
*/ | |||
const suggestions = [{ link: '#', text: 'foo' }, { link: '#', text: 'bar' }]; | |||
export default function TutorialsHelp({ onTutorialSelect } /*: Props */) { | |||
const handleClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
onTutorialSelect(); | |||
}; | |||
return ( | |||
<div> | |||
<h2 className="spacer-top spacer-bottom">{translate('help.section.tutorials')}</h2> | |||
<a href="#" onClick={handleClick}> | |||
{translate('tutorials.onboarding')} | |||
</a> | |||
</div> | |||
); | |||
} | |||
it('should display suggestion links', () => { | |||
const context = {}; | |||
const wrapper = shallow(<EmbedDocsPopups onClose={jest.fn()} suggestions={suggestions} />, { | |||
context | |||
}); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,151 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display suggestion links 1`] = ` | |||
<BubblePopup | |||
customClass="bubble-popup-bottom bubble-popup-menu abs-width-240 embed-docs-popup" | |||
> | |||
<ul | |||
className="menu" | |||
> | |||
<React.Fragment> | |||
<li | |||
className="dropdown-header" | |||
> | |||
embed_docs.suggestion | |||
</li> | |||
<li | |||
key="0" | |||
> | |||
<a | |||
href="#" | |||
target="_blank" | |||
> | |||
foo | |||
<i | |||
className="icon-detach" | |||
/> | |||
</a> | |||
</li> | |||
<li | |||
key="1" | |||
> | |||
<a | |||
href="#" | |||
target="_blank" | |||
> | |||
bar | |||
<i | |||
className="icon-detach" | |||
/> | |||
</a> | |||
</li> | |||
<li | |||
className="divider" | |||
/> | |||
</React.Fragment> | |||
<li> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
target="_blank" | |||
to="/documentation" | |||
> | |||
embed_docs.documentation_index | |||
<i | |||
className="icon-detach" | |||
/> | |||
</Link> | |||
</li> | |||
<React.Fragment> | |||
<li> | |||
<a | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
embed_docs.analyze_new_project | |||
</a> | |||
</li> | |||
<li | |||
className="divider" | |||
/> | |||
<li | |||
className="dropdown-header" | |||
> | |||
embed_docs.get_support | |||
</li> | |||
<li> | |||
<a | |||
href="https://groups.google.com/forum/#!forum/sonarqube" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
<img | |||
alt="Google Groups" | |||
className="spacer-right" | |||
height="18" | |||
src="/images/embed-doc/google-group-icon.svg" | |||
width="18" | |||
/> | |||
Google Groups | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
href="http://stackoverflow.com/questions/tagged/sonarqube" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
<img | |||
alt="Stack Overflow" | |||
className="spacer-right" | |||
height="18" | |||
src="/images/embed-doc/so-icon.svg" | |||
width="18" | |||
/> | |||
Stack Overflow | |||
</a> | |||
</li> | |||
<li | |||
className="dropdown-header" | |||
> | |||
embed_docs.stay_connected | |||
</li> | |||
<li> | |||
<a | |||
href="https://blog.sonarsource.com/" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
<img | |||
alt="Product News" | |||
className="spacer-right" | |||
height="18" | |||
src="/images/embed-doc/sq-icon.svg" | |||
width="18" | |||
/> | |||
Product News | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
href="https://twitter.com/SonarQube" | |||
rel="noopener noreferrer" | |||
target="_blank" | |||
> | |||
<img | |||
alt="Twitter" | |||
className="spacer-right" | |||
height="18" | |||
src="/images/embed-doc/twitter-icon.svg" | |||
width="18" | |||
/> | |||
</a> | |||
</li> | |||
</React.Fragment> | |||
</ul> | |||
</BubblePopup> | |||
`; |
@@ -1,118 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import LinksHelp from './LinksHelp'; | |||
import LinksHelpSonarCloud from './LinksHelpSonarCloud'; | |||
import ShortcutsHelp from './ShortcutsHelp'; | |||
import TutorialsHelp from './TutorialsHelp'; | |||
import Modal from '../../../components/controls/Modal'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
currentUser: { isLoggedIn: boolean }, | |||
onClose: () => void, | |||
onTutorialSelect: () => void, | |||
onSonarCloud?: boolean | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
section: string | |||
}; | |||
*/ | |||
export default class GlobalHelp extends React.PureComponent { | |||
/*:: props: Props; */ | |||
state /*: State */ = { section: 'shortcuts' }; | |||
handleCloseClick = (event /*: Event */) => { | |||
event.preventDefault(); | |||
this.props.onClose(); | |||
}; | |||
handleSectionClick = (event /*: Event & { currentTarget: HTMLElement } */) => { | |||
event.preventDefault(); | |||
const { section } = event.currentTarget.dataset; | |||
this.setState({ section }); | |||
}; | |||
renderSection = () => { | |||
switch (this.state.section) { | |||
case 'shortcuts': | |||
return <ShortcutsHelp />; | |||
case 'links': | |||
return this.props.onSonarCloud ? ( | |||
<LinksHelpSonarCloud onClose={this.props.onClose} /> | |||
) : ( | |||
<LinksHelp onClose={this.props.onClose} /> | |||
); | |||
case 'tutorials': | |||
return <TutorialsHelp onTutorialSelect={this.props.onTutorialSelect} />; | |||
default: | |||
return null; | |||
} | |||
}; | |||
renderMenuItem = (section /*: string */) => ( | |||
<li key={section}> | |||
<a | |||
className={classNames({ active: section === this.state.section })} | |||
data-section={section} | |||
href="#" | |||
onClick={this.handleSectionClick}> | |||
{translate('help.section', section)} | |||
</a> | |||
</li> | |||
); | |||
renderMenu = () => ( | |||
<ul className="side-tabs-menu"> | |||
{(this.props.currentUser.isLoggedIn && !this.props.onSonarCloud | |||
? ['shortcuts', 'tutorials', 'links'] | |||
: ['shortcuts', 'links'] | |||
).map(this.renderMenuItem)} | |||
</ul> | |||
); | |||
render() { | |||
return ( | |||
<Modal contentLabel={translate('help')} medium={true} onRequestClose={this.props.onClose}> | |||
<div className="modal-head"> | |||
<h2>{translate('help')}</h2> | |||
</div> | |||
<div className="side-tabs-layout"> | |||
<div className="side-tabs-side">{this.renderMenu()}</div> | |||
<div className="side-tabs-main">{this.renderSection()}</div> | |||
</div> | |||
<div className="modal-foot"> | |||
<a className="js-modal-close" href="#" onClick={this.handleCloseClick}> | |||
{translate('close')} | |||
</a> | |||
</div> | |||
</Modal> | |||
); | |||
} | |||
} |
@@ -1,69 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { onClose: () => void }; | |||
*/ | |||
export default function LinksHelp({ onClose } /*: Props */) { | |||
return ( | |||
<div> | |||
<h2 className="spacer-top spacer-bottom">{translate('help.section.links')}</h2> | |||
<p className="spacer-bottom"> | |||
<a href="http://www.sonarqube.org">{translate('footer.community')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://redirect.sonarsource.com/doc/home.html"> | |||
{translate('footer.documentation')} | |||
</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://redirect.sonarsource.com/doc/community.html"> | |||
{translate('footer.support')} | |||
</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://redirect.sonarsource.com/doc/plugin-library.html"> | |||
{translate('footer.plugins')} | |||
</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<Link to="/web_api" onClick={onClose}> | |||
{translate('footer.web_api')} | |||
</Link> | |||
</p> | |||
<p> | |||
<Link to="/about" onClick={onClose}> | |||
{translate('footer.about')} | |||
</Link> | |||
</p> | |||
</div> | |||
); | |||
} |
@@ -1,65 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { Link } from 'react-router'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { onClose: () => void }; | |||
*/ | |||
export default function LinksHelpSonarCloud({ onClose } /*: Props */) { | |||
return ( | |||
<div> | |||
<h2 className="spacer-top spacer-bottom">{translate('help.section.links')}</h2> | |||
<p className="spacer-bottom"> | |||
<a href="https://about.sonarcloud.io/news/">{translate('footer.news')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://about.sonarcloud.io/terms.pdf">{translate('footer.terms')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://twitter.com/sonarqube">{translate('footer.twitter')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://about.sonarcloud.io/get-started/">{translate('footer.get_started')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<a href="https://about.sonarcloud.io/contact/">{translate('footer.help')}</a> | |||
</p> | |||
<p className="spacer-bottom"> | |||
<Link to="/web_api" onClick={onClose}> | |||
{translate('footer.web_api')} | |||
</Link> | |||
</p> | |||
<p> | |||
<a href="https://about.sonarcloud.io/">{translate('footer.about')}</a> | |||
</p> | |||
</div> | |||
); | |||
} |
@@ -1,135 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { translate } from '../../../helpers/l10n'; | |||
export default function ShortcutsHelp() { | |||
return ( | |||
<div> | |||
<h2 className="spacer-top spacer-bottom">{translate('help.section.shortcuts')}</h2> | |||
<div className="columns"> | |||
<div className="column-half"> | |||
<div className="spacer-bottom"> | |||
<h3 className="shortcuts-section-title">{translate('shortcuts.section.global')}</h3> | |||
<ul className="shortcuts-list"> | |||
<li> | |||
<span className="shortcut-button spacer-right">s</span> | |||
{translate('shortcuts.section.global.search')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">?</span> | |||
{translate('shortcuts.section.global.shortcuts')} | |||
</li> | |||
</ul> | |||
</div> | |||
<h3 className="shortcuts-section-title">{translate('shortcuts.section.rules')}</h3> | |||
<ul className="shortcuts-list"> | |||
<li> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button spacer-right">↓</span> | |||
{translate('shortcuts.section.rules.navigate_between_rules')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">→</span> | |||
{translate('shortcuts.section.rules.open_details')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">←</span> | |||
{translate('shortcuts.section.rules.return_to_list')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">a</span> | |||
{translate('shortcuts.section.rules.activate')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">d</span> | |||
{translate('shortcuts.section.rules.deactivate')} | |||
</li> | |||
</ul> | |||
</div> | |||
<div className="column-half"> | |||
<h3 className="shortcuts-section-title">{translate('shortcuts.section.issues')}</h3> | |||
<ul className="shortcuts-list"> | |||
<li> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button spacer-right">↓</span> | |||
{translate('shortcuts.section.issues.navigate_between_issues')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">→</span> | |||
{translate('shortcuts.section.issues.open_details')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">←</span> | |||
{translate('shortcuts.section.issues.return_to_list')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button little-spacer-right">alt</span> | |||
<span className="little-spacer-right">+</span> | |||
<span className="shortcut-button little-spacer-right">↑</span> | |||
<span className="shortcut-button spacer-right">↓</span> | |||
{translate('issues.to_navigate_issue_locations')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button little-spacer-right">alt</span> | |||
<span className="little-spacer-right">+</span> | |||
<span className="shortcut-button little-spacer-right">←</span> | |||
<span className="shortcut-button spacer-right">→</span> | |||
{translate('issues.to_switch_flows')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">f</span> | |||
{translate('shortcuts.section.issue.do_transition')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">a</span> | |||
{translate('shortcuts.section.issue.assign')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">m</span> | |||
{translate('shortcuts.section.issue.assign_to_me')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">i</span> | |||
{translate('shortcuts.section.issue.change_severity')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">c</span> | |||
{translate('shortcuts.section.issue.comment')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button little-spacer-right">ctrl</span> | |||
<span className="shortcut-button spacer-right">enter</span> | |||
{translate('shortcuts.section.issue.submit_comment')} | |||
</li> | |||
<li> | |||
<span className="shortcut-button spacer-right">t</span> | |||
{translate('shortcuts.section.issue.change_tags')} | |||
</li> | |||
</ul> | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
} |
@@ -1,70 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import GlobalHelp from '../GlobalHelp'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
it('switches between tabs', () => { | |||
const wrapper = shallow( | |||
<GlobalHelp | |||
currentUser={{ isLoggedIn: true }} | |||
onClose={jest.fn()} | |||
onTutorialSelect={jest.fn()} | |||
/> | |||
); | |||
expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); | |||
clickOnSection(wrapper, 'links'); | |||
expect(wrapper.find('LinksHelp')).toHaveLength(1); | |||
clickOnSection(wrapper, 'tutorials'); | |||
expect(wrapper.find('TutorialsHelp')).toHaveLength(1); | |||
clickOnSection(wrapper, 'shortcuts'); | |||
expect(wrapper.find('ShortcutsHelp')).toHaveLength(1); | |||
}); | |||
it('does not show tutorials for anonymous', () => { | |||
expect( | |||
shallow( | |||
<GlobalHelp | |||
currentUser={{ isLoggedIn: false }} | |||
onClose={jest.fn()} | |||
onTutorialSelect={jest.fn()} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('display special links page for SonarCloud', () => { | |||
const wrapper = shallow( | |||
<GlobalHelp | |||
currentUser={{ isLoggedIn: false }} | |||
onClose={jest.fn()} | |||
onTutorialSelect={jest.fn()} | |||
onSonarCloud={true} | |||
/> | |||
); | |||
clickOnSection(wrapper, 'links'); | |||
expect(wrapper.find('LinksHelpSonarCloud')).toHaveLength(1); | |||
}); | |||
function clickOnSection(wrapper /*: Object */, section /*: string */) { | |||
click(wrapper.find(`[data-section="${section}"]`), { currentTarget: { dataset: { section } } }); | |||
} |
@@ -1,69 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`does not show tutorials for anonymous 1`] = ` | |||
<Modal | |||
contentLabel="help" | |||
medium={true} | |||
onRequestClose={[MockFunction]} | |||
> | |||
<div | |||
className="modal-head" | |||
> | |||
<h2> | |||
help | |||
</h2> | |||
</div> | |||
<div | |||
className="side-tabs-layout" | |||
> | |||
<div | |||
className="side-tabs-side" | |||
> | |||
<ul | |||
className="side-tabs-menu" | |||
> | |||
<li | |||
key="shortcuts" | |||
> | |||
<a | |||
className="active" | |||
data-section="shortcuts" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
help.section.shortcuts | |||
</a> | |||
</li> | |||
<li | |||
key="links" | |||
> | |||
<a | |||
className="" | |||
data-section="links" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
help.section.links | |||
</a> | |||
</li> | |||
</ul> | |||
</div> | |||
<div | |||
className="side-tabs-main" | |||
> | |||
<ShortcutsHelp /> | |||
</div> | |||
</div> | |||
<div | |||
className="modal-foot" | |||
> | |||
<a | |||
className="js-modal-close" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
close | |||
</a> | |||
</div> | |||
</Modal> | |||
`; |
@@ -25,16 +25,16 @@ import GlobalNavExplore from './GlobalNavExplore'; | |||
import GlobalNavUserContainer from './GlobalNavUserContainer'; | |||
import GlobalNavPlus from './GlobalNavPlus'; | |||
import Search from '../../search/Search'; | |||
import GlobalHelp from '../../help/GlobalHelp'; | |||
import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper'; | |||
import * as theme from '../../../theme'; | |||
import { isLoggedIn, CurrentUser, AppState } from '../../../types'; | |||
import OnboardingModal from '../../../../apps/tutorials/onboarding/OnboardingModal'; | |||
import NavBar from '../../../../components/nav/NavBar'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
import HelpIcon from '../../../../components/icons-components/HelpIcon'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { getCurrentUser, getAppState, getGlobalSettingValue } from '../../../../store/rootReducer'; | |||
import { skipOnboarding } from '../../../../store/users/actions'; | |||
import { SuggestionLink } from '../../embed-docs-modal/SuggestionsProvider'; | |||
import './GlobalNav.css'; | |||
interface StateProps { | |||
@@ -52,6 +52,7 @@ interface Props extends StateProps, DispatchProps { | |||
isOnboardingTutorialOpen: boolean; | |||
location: { pathname: string }; | |||
openOnboardingTutorial: () => void; | |||
suggestions: Array<SuggestionLink>; | |||
} | |||
interface State { | |||
@@ -64,7 +65,6 @@ class GlobalNav extends React.PureComponent<Props, State> { | |||
state: State = { helpOpen: false, onboardingTutorialTooltip: false }; | |||
componentDidMount() { | |||
window.addEventListener('keypress', this.onKeyPress); | |||
if (this.props.currentUser.showOnboardingTutorial) { | |||
this.openOnboardingTutorial(); | |||
} | |||
@@ -74,28 +74,8 @@ class GlobalNav extends React.PureComponent<Props, State> { | |||
if (this.interval) { | |||
clearInterval(this.interval); | |||
} | |||
window.removeEventListener('keypress', this.onKeyPress); | |||
} | |||
onKeyPress = (event: KeyboardEvent) => { | |||
const { tagName } = event.target as HTMLElement; | |||
const code = event.keyCode || event.which; | |||
const isInput = tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA'; | |||
const isTriggerKey = code === 63; | |||
if (!isInput && isTriggerKey) { | |||
this.openHelp(); | |||
} | |||
}; | |||
handleHelpClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.openHelp(); | |||
}; | |||
openHelp = () => this.setState({ helpOpen: true }); | |||
closeHelp = () => this.setState({ helpOpen: false }); | |||
openOnboardingTutorial = () => { | |||
this.setState({ helpOpen: false }); | |||
this.props.openOnboardingTutorial(); | |||
@@ -120,13 +100,11 @@ class GlobalNav extends React.PureComponent<Props, State> { | |||
<ul className="global-navbar-menu pull-right"> | |||
<GlobalNavExplore location={this.props.location} onSonarCloud={this.props.onSonarCloud} /> | |||
<li> | |||
<Tooltip | |||
overlay={this.props.onSonarCloud ? undefined : translate('tutorials.follow_later')} | |||
visible={this.state.onboardingTutorialTooltip}> | |||
<a className="navbar-help" href="#" onClick={this.handleHelpClick}> | |||
<HelpIcon /> | |||
</a> | |||
</Tooltip> | |||
<EmbedDocsPopupHelper | |||
showTooltip={this.state.onboardingTutorialTooltip} | |||
suggestions={this.props.suggestions} | |||
tooltip={!this.props.onSonarCloud} | |||
/> | |||
</li> | |||
<Search appState={this.props.appState} currentUser={this.props.currentUser} /> | |||
{isLoggedIn(this.props.currentUser) && | |||
@@ -140,15 +118,6 @@ class GlobalNav extends React.PureComponent<Props, State> { | |||
<GlobalNavUserContainer {...this.props} /> | |||
</ul> | |||
{this.state.helpOpen && ( | |||
<GlobalHelp | |||
currentUser={this.props.currentUser} | |||
onClose={this.closeHelp} | |||
onSonarCloud={this.props.onSonarCloud} | |||
onTutorialSelect={this.openOnboardingTutorial} | |||
/> | |||
)} | |||
{this.props.isOnboardingTutorialOpen && ( | |||
<OnboardingModal onFinish={this.closeOnboardingTutorial} /> | |||
)} |
@@ -59,6 +59,7 @@ module.exports = { | |||
gridSize: `${grid}px`, | |||
baseFontSize: '13px', | |||
verySmallFontSize: '10px', | |||
smallFontSize: '12px', | |||
mediumFontSize: '14px', | |||
bigFontSize: '16px', |
@@ -24,6 +24,7 @@ import { omitBy } from 'lodash'; | |||
import PageHeader from './PageHeader'; | |||
import ProjectsList from './ProjectsList'; | |||
import PageSidebar from './PageSidebar'; | |||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||
import Visualizations from '../visualizations/Visualizations'; | |||
import { CurrentUser, isLoggedIn } from '../../../app/types'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
@@ -317,6 +318,7 @@ export default class AllProjects extends React.PureComponent<Props, State> { | |||
render() { | |||
return ( | |||
<div className="layout-page projects-page" id="projects-page"> | |||
<Suggestions suggestions="projects" /> | |||
<Helmet title={translate('projects.page')} /> | |||
{this.renderSide()} |
@@ -19,7 +19,7 @@ | |||
*/ | |||
/* eslint-disable import/order */ | |||
import * as React from 'react'; | |||
import { mount, shallow } from 'enzyme'; | |||
import { shallow } from 'enzyme'; | |||
import AllProjects, { Props } from '../AllProjects'; | |||
import { get, save } from '../../../../helpers/storage'; | |||
@@ -71,7 +71,7 @@ it('renders', () => { | |||
}); | |||
it('fetches projects', () => { | |||
mountRender(); | |||
shallowRender(); | |||
expect(fetchProjects).lastCalledWith( | |||
{ | |||
coverage: undefined, | |||
@@ -104,7 +104,7 @@ it('redirects to the saved search', () => { | |||
(key: string) => (key === 'sonarqube.projects.view' ? 'leak' : null) | |||
); | |||
const replace = jest.fn(); | |||
mountRender({}, jest.fn(), replace); | |||
shallowRender({}, jest.fn(), replace); | |||
expect(replace).lastCalledWith({ pathname: '/projects', query: { view: 'leak' } }); | |||
}); | |||
@@ -161,19 +161,6 @@ it('changes perspective to risk visualization', () => { | |||
expect(save).toHaveBeenCalledWith('sonarqube.projects.visualization', 'risk', undefined); | |||
}); | |||
function mountRender(props: any = {}, push: Function = jest.fn(), replace: Function = jest.fn()) { | |||
return mount( | |||
<AllProjects | |||
currentUser={{ isLoggedIn: true }} | |||
fetchProjects={jest.fn()} | |||
isFavorite={false} | |||
location={{ pathname: '/projects', query: {} }} | |||
{...props} | |||
/>, | |||
{ context: { router: { push, replace } } } | |||
); | |||
} | |||
function shallowRender( | |||
props: Partial<Props> = {}, | |||
push: Function = jest.fn(), |
@@ -5,6 +5,9 @@ exports[`renders 1`] = ` | |||
className="layout-page projects-page" | |||
id="projects-page" | |||
> | |||
<Suggestions | |||
suggestions="projects" | |||
/> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
@@ -134,6 +137,9 @@ exports[`renders 2`] = ` | |||
className="layout-page projects-page" | |||
id="projects-page" | |||
> | |||
<Suggestions | |||
suggestions="projects" | |||
/> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} |
@@ -29,7 +29,7 @@ export interface BubblePopupPosition { | |||
interface Props { | |||
customClass?: string; | |||
children: React.ReactNode; | |||
position: BubblePopupPosition; | |||
position?: BubblePopupPosition; | |||
} | |||
/** |
@@ -19,6 +19,7 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { BubblePopupPosition } from './BubblePopup'; | |||
interface Props { | |||
className?: string; | |||
@@ -31,7 +32,7 @@ interface Props { | |||
} | |||
interface State { | |||
position: { top: number; left?: number; right?: number }; | |||
position: BubblePopupPosition; | |||
} | |||
export default class BubblePopupHelper extends React.PureComponent<Props, State> { |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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. | |||
*/ | |||
declare module '*.json' { | |||
const value: any; | |||
export default value; | |||
} |
@@ -8290,9 +8290,9 @@ typescript-eslint-parser@15.0.0: | |||
lodash.unescape "4.0.1" | |||
semver "5.5.0" | |||
typescript@2.8.1: | |||
version "2.8.1" | |||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.1.tgz#6160e4f8f195d5ba81d4876f9c0cc1fbc0820624" | |||
typescript@2.8.3: | |||
version "2.8.3" | |||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" | |||
ua-parser-js@^0.7.9: | |||
version "0.7.17" |
@@ -2545,6 +2545,18 @@ organization.change_visibility_form.header=Set Default Visibility of New Project | |||
organization.change_visibility_form.warning=This will not change the visibility of already existing projects. | |||
organization.change_visibility_form.submit=Change Default Visibility | |||
#------------------------------------------------------------------------------ | |||
# | |||
# EMBEDED DOCS | |||
# | |||
#------------------------------------------------------------------------------ | |||
embed_docs.suggestion=Suggestions For This Page | |||
embed_docs.documentation_index=Documentation index | |||
embed_docs.get_support=Get Support | |||
embed_docs.stay_connected=Stay Connected | |||
embed_docs.contact_form=Contact form | |||
embed_docs.analyze_new_project=Analyze new project | |||
#------------------------------------------------------------------------------ | |||
# | |||
# GLOBAL FOOTER |