ソースを参照

SONAR-10609 Rework the global help modal to a dropdown (#177)

tags/7.5
Pascal Mugnier 6年前
コミット
4a31f1ff8f
33個のファイルの変更663行の追加637行の削除
  1. 5
    1
      server/sonar-web/.eslintrc
  2. 1
    1
      server/sonar-web/package.json
  3. 1
    0
      server/sonar-web/public/images/embed-doc/google-group-icon.svg
  4. 1
    0
      server/sonar-web/public/images/embed-doc/sc-icon.svg
  5. 1
    0
      server/sonar-web/public/images/embed-doc/so-icon.svg
  6. 1
    0
      server/sonar-web/public/images/embed-doc/sq-icon.svg
  7. 1
    0
      server/sonar-web/public/images/embed-doc/twitter-icon.svg
  8. 1
    2
      server/sonar-web/scripts/start.js
  9. 22
    16
      server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
  10. 159
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx
  11. 95
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx
  12. 11
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json
  13. 53
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx
  14. 3
    10
      server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts
  15. 84
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx
  16. 12
    21
      server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx
  17. 151
    0
      server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap
  18. 0
    118
      server/sonar-web/src/main/js/app/components/help/GlobalHelp.js
  19. 0
    69
      server/sonar-web/src/main/js/app/components/help/LinksHelp.js
  20. 0
    65
      server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js
  21. 0
    135
      server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js
  22. 0
    70
      server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js
  23. 0
    69
      server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap
  24. 8
    39
      server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx
  25. 1
    0
      server/sonar-web/src/main/js/app/theme.js
  26. 2
    0
      server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
  27. 3
    16
      server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
  28. 6
    0
      server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
  29. 1
    1
      server/sonar-web/src/main/js/components/common/BubblePopup.tsx
  30. 2
    1
      server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx
  31. 23
    0
      server/sonar-web/src/main/js/typings/json.d.ts
  32. 3
    3
      server/sonar-web/yarn.lock
  33. 12
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 5
- 1
server/sonar-web/.eslintrc ファイルの表示

@@ -1,3 +1,7 @@
{
"extends": "sonarqube"
"extends": "sonarqube",

"rules": {
"import/extensions": ["error", "never", { "json": "always" }]
}
}

+ 1
- 1
server/sonar-web/package.json ファイルの表示

@@ -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",

+ 1
- 0
server/sonar-web/public/images/embed-doc/google-group-icon.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="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>

+ 1
- 0
server/sonar-web/public/images/embed-doc/sc-icon.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>

+ 1
- 0
server/sonar-web/public/images/embed-doc/so-icon.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>

+ 1
- 0
server/sonar-web/public/images/embed-doc/sq-icon.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>

+ 1
- 0
server/sonar-web/public/images/embed-doc/twitter-icon.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>

+ 1
- 2
server/sonar-web/scripts/start.js ファイルの表示

@@ -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
- 16
server/sonar-web/src/main/js/app/components/GlobalContainer.tsx ファイルの表示

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

+ 159
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopup.tsx ファイルの表示

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

+ 95
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsPopupHelper.tsx ファイルの表示

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

+ 11
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/EmbedDocsSuggestions.json ファイルの表示

@@ -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 " }
]
}

+ 53
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/Suggestions.tsx ファイルの表示

@@ -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;
}
}

server/sonar-web/src/main/js/app/components/help/GlobalHelp.d.ts → server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsContext.ts ファイルの表示

@@ -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> {}

+ 84
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/SuggestionsProvider.tsx ファイルの表示

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

server/sonar-web/src/main/js/app/components/help/TutorialsHelp.js → server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/EmbedDocsPopup-test.tsx ファイルの表示

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

+ 151
- 0
server/sonar-web/src/main/js/app/components/embed-docs-modal/__tests__/__snapshots__/EmbedDocsPopup-test.tsx.snap ファイルの表示

@@ -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"
/>
Twitter
</a>
</li>
</React.Fragment>
</ul>
</BubblePopup>
`;

+ 0
- 118
server/sonar-web/src/main/js/app/components/help/GlobalHelp.js ファイルの表示

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

+ 0
- 69
server/sonar-web/src/main/js/app/components/help/LinksHelp.js ファイルの表示

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

+ 0
- 65
server/sonar-web/src/main/js/app/components/help/LinksHelpSonarCloud.js ファイルの表示

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

+ 0
- 135
server/sonar-web/src/main/js/app/components/help/ShortcutsHelp.js ファイルの表示

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

+ 0
- 70
server/sonar-web/src/main/js/app/components/help/__tests__/GlobalHelp-test.js ファイルの表示

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

+ 0
- 69
server/sonar-web/src/main/js/app/components/help/__tests__/__snapshots__/GlobalHelp-test.js.snap ファイルの表示

@@ -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>
`;

+ 8
- 39
server/sonar-web/src/main/js/app/components/nav/global/GlobalNav.tsx ファイルの表示

@@ -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} />
)}

+ 1
- 0
server/sonar-web/src/main/js/app/theme.js ファイルの表示

@@ -59,6 +59,7 @@ module.exports = {
gridSize: `${grid}px`,

baseFontSize: '13px',
verySmallFontSize: '10px',
smallFontSize: '12px',
mediumFontSize: '14px',
bigFontSize: '16px',

+ 2
- 0
server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx ファイルの表示

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

+ 3
- 16
server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx ファイルの表示

@@ -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(),

+ 6
- 0
server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap ファイルの表示

@@ -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}

+ 1
- 1
server/sonar-web/src/main/js/components/common/BubblePopup.tsx ファイルの表示

@@ -29,7 +29,7 @@ export interface BubblePopupPosition {
interface Props {
customClass?: string;
children: React.ReactNode;
position: BubblePopupPosition;
position?: BubblePopupPosition;
}

/**

+ 2
- 1
server/sonar-web/src/main/js/components/common/BubblePopupHelper.tsx ファイルの表示

@@ -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> {

+ 23
- 0
server/sonar-web/src/main/js/typings/json.d.ts ファイルの表示

@@ -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;
}

+ 3
- 3
server/sonar-web/yarn.lock ファイルの表示

@@ -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"

+ 12
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties ファイルの表示

@@ -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

読み込み中…
キャンセル
保存