* SONAR-11040 Update tutorial choices modal * SONAR-11041 Migrate manual installation tab * SONAR-11041 Rename button to start new project tutorial * SONAR-11041 Rework sonarcloud tabbed page styling * SONAR-11042 Add alm app install buttons in create project page * Make start script compatible with ALM integrationtags/7.5
@@ -111,7 +111,9 @@ function runDevServer(compiler, host, port, protocol) { | |||
proxy: { | |||
'/api': { target: proxy, changeOrigin: true }, | |||
'/static': { target: proxy, changeOrigin: true }, | |||
'/integration': { target: proxy, changeOrigin: true } | |||
'/integration': { target: proxy, changeOrigin: true }, | |||
'/sessions/init': { target: proxy, changeOrigin: true }, | |||
'/oauth2': { target: proxy, changeOrigin: true } | |||
} | |||
}); | |||
@@ -17,10 +17,14 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { SearchProjectsResponseComponent } from '../../api/components'; | |||
import { getJSON } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
export const PAGE_SIZE = 50; | |||
export const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP']; | |||
export type Project = SearchProjectsResponseComponent; | |||
export function getRepositories(): Promise<{ | |||
installation: { | |||
installationUrl: string; | |||
enabled: boolean; | |||
}; | |||
}> { | |||
return getJSON('/api/alm_integration/list_repositories').catch(throwGlobalError); | |||
} |
@@ -17,8 +17,8 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { getJSON, postJSON, post, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { getJSON, postJSON, post, RequestData } from '../helpers/request'; | |||
import { Paging, Visibility, BranchParameters, MyProject } from '../app/types'; | |||
export interface BaseSearchProjectsParameters { | |||
@@ -31,29 +31,30 @@ export interface BaseSearchProjectsParameters { | |||
visibility?: Visibility; | |||
} | |||
export interface SearchProjectsParameters extends BaseSearchProjectsParameters { | |||
p?: number; | |||
ps?: number; | |||
export interface ProjectBase { | |||
key: string; | |||
name: string; | |||
qualifier: string; | |||
visibility: Visibility; | |||
} | |||
export interface SearchProjectsResponseComponent { | |||
export interface Project extends ProjectBase { | |||
id: string; | |||
key: string; | |||
lastAnalysisDate?: string; | |||
name: string; | |||
organization: string; | |||
qualifier: string; | |||
visibility: Visibility; | |||
} | |||
export interface SearchProjectsResponse { | |||
components: SearchProjectsResponseComponent[]; | |||
paging: Paging; | |||
export interface SearchProjectsParameters extends BaseSearchProjectsParameters { | |||
p?: number; | |||
ps?: number; | |||
} | |||
export function getComponents( | |||
parameters: SearchProjectsParameters | |||
): Promise<SearchProjectsResponse> { | |||
): Promise<{ | |||
components: Project[]; | |||
paging: Paging; | |||
}> { | |||
return getJSON('/api/projects/search', parameters); | |||
} | |||
@@ -75,7 +76,7 @@ export function createProject(data: { | |||
name: string; | |||
project: string; | |||
organization?: string; | |||
}): Promise<any> { | |||
}): Promise<{ project: ProjectBase }> { | |||
return postJSON('/api/projects/create', data).catch(throwGlobalError); | |||
} | |||
@@ -99,11 +99,13 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
this.tryAutoOpenLicense().catch(this.tryAutoOpenOnboarding); | |||
} | |||
closeOnboarding = () => { | |||
closeOnboarding = (doSkipOnboarding = true) => { | |||
this.setState(state => { | |||
if (state.modal !== ModalKey.license) { | |||
skipOnboarding(); | |||
this.props.skipOnboardingAction(); | |||
if (doSkipOnboarding) { | |||
skipOnboarding(); | |||
this.props.skipOnboardingAction(); | |||
} | |||
return { automatic: false, modal: undefined }; | |||
} | |||
return undefined; | |||
@@ -164,7 +166,10 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
tryAutoOpenOnboarding = () => { | |||
const { currentUser, location } = this.props; | |||
if (currentUser.showOnboardingTutorial && !location.pathname.startsWith('documentation')) { | |||
if ( | |||
currentUser.showOnboardingTutorial && | |||
!['about', 'documentation', 'onboarding'].some(path => location.pathname.startsWith(path)) | |||
) { | |||
this.setState({ automatic: true }); | |||
if (isSonarCloud()) { | |||
this.openOnboarding(); | |||
@@ -182,9 +187,8 @@ export class StartupModal extends React.PureComponent<Props, State> { | |||
{modal === ModalKey.license && <LicensePromptModal onClose={this.closeLicense} />} | |||
{modal === ModalKey.onboarding && ( | |||
<Onboarding | |||
onFinish={this.closeOnboarding} | |||
onClose={this.closeOnboarding} | |||
onOpenOrganizationOnboarding={this.openOrganizationOnboarding} | |||
onOpenProjectOnboarding={this.openProjectOnboarding} | |||
onOpenTeamOnboarding={this.openTeamOnboarding} | |||
/> | |||
)} |
@@ -69,7 +69,7 @@ export default class GlobalNavPlus extends React.PureComponent<Props, State> { | |||
<ul className="menu"> | |||
<li> | |||
<a className="js-new-project" href="#" onClick={this.handleNewProjectClick}> | |||
{translate('my_account.analyze_new_project')} | |||
{translate('provisioning.create_new_project')} | |||
</a> | |||
</li> | |||
<li className="divider" /> |
@@ -12,7 +12,7 @@ exports[`render 1`] = ` | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
my_account.analyze_new_project | |||
provisioning.create_new_project | |||
</a> | |||
</li> | |||
<li |
@@ -60,14 +60,21 @@ | |||
color: #3c763d; | |||
} | |||
.alert-muted { | |||
color: var(--disableGrayText); | |||
border-color: var(--disableGrayBorder); | |||
background-color: var(--disableGrayBg); | |||
} | |||
.alert-big { | |||
font-size: var(--mediumFontSize); | |||
padding: 10px 16px; | |||
} | |||
.page-header .alert { | |||
clear: left; | |||
float: left; | |||
.alert-small { | |||
font-size: var(--verySmallFontSize); | |||
margin-bottom: 0; | |||
padding: 2px 4px; | |||
} | |||
.page-notifs .alert { |
@@ -101,6 +101,24 @@ | |||
padding: 10px; | |||
} | |||
.modal-simple-head { | |||
padding: calc(2 * var(--pagePadding)) calc(3 * var(--pagePadding)); | |||
} | |||
.modal-simple-head h1 { | |||
font-size: var(--hugeFontSize); | |||
font-weight: bold; | |||
line-height: 30px; | |||
} | |||
.modal-simple-body { | |||
padding: 0 calc(3 * var(--pagePadding)); | |||
} | |||
.modal-simple-footer { | |||
padding: calc(2 * var(--pagePadding)) calc(3 * var(--pagePadding)); | |||
} | |||
.modal-field, | |||
.modal-large-field, | |||
.modal-validation-field { |
@@ -172,6 +172,18 @@ label[for] { | |||
cursor: pointer; | |||
} | |||
.form-field { | |||
clear: both; | |||
display: block; | |||
padding-top: var(--gridSize); | |||
padding-bottom: calc(2 * var(--gridSize)); | |||
} | |||
.form-field label { | |||
display: block; | |||
padding-bottom: var(--gridSize); | |||
} | |||
.radio-toggle { | |||
display: inline-block; | |||
vertical-align: middle; |
@@ -0,0 +1,67 @@ | |||
/* | |||
* 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. | |||
*/ | |||
/* EXTENDS components/pages.css */ | |||
.sonarcloud.page-limited { | |||
padding-top: 50px; | |||
padding-bottom: 50px; | |||
} | |||
.sonarcloud .page-header { | |||
margin-bottom: 40px; | |||
} | |||
.sonarcloud .page-title { | |||
font-size: var(--hugeFontSize); | |||
font-weight: bold; | |||
} | |||
.sonarcloud .flex-tabs { | |||
display: flex; | |||
clear: left; | |||
margin-bottom: calc(3 * var(--gridSize)); | |||
box-shadow: 0 1px 0 var(--barBorderColor); | |||
} | |||
.sonarcloud .flex-tabs > li > a { | |||
display: block; | |||
height: 100%; | |||
width: 100%; | |||
box-sizing: border-box; | |||
color: var(--secondFontColor); | |||
font-weight: 600; | |||
cursor: pointer; | |||
padding-bottom: var(--gridSize); | |||
border-bottom: 2px solid transparent; | |||
transition: color 0.2s ease; | |||
} | |||
.sonarcloud .flex-tabs > li ~ li { | |||
margin-left: calc(4 * var(--gridSize)); | |||
} | |||
.sonarcloud .flex-tabs > li > a:hover { | |||
color: var(--baseFontColor); | |||
} | |||
.sonarcloud .flex-tabs > li > a.selected { | |||
color: var(--blue); | |||
border-bottom-color: var(--blue); | |||
} |
@@ -64,6 +64,7 @@ module.exports = { | |||
smallFontSize: '12px', | |||
mediumFontSize: '14px', | |||
bigFontSize: '16px', | |||
hugeFontSize: '24px', | |||
controlHeight: `${3 * grid}px`, | |||
smallControlHeight: `${2.5 * grid}px`, |
@@ -305,6 +305,8 @@ export interface LinearIssueLocation { | |||
export interface LoggedInUser extends CurrentUser { | |||
avatar?: string; | |||
email?: string; | |||
externalIdentity?: string; | |||
externalProvider?: string; | |||
homepage?: HomePage; | |||
isLoggedIn: true; | |||
login: string; |
@@ -45,6 +45,7 @@ import IssuesPageSelector from '../../apps/issues/IssuesPageSelector'; | |||
import marketplaceRoutes from '../../apps/marketplace/routes'; | |||
import customMetricsRoutes from '../../apps/custom-metrics/routes'; | |||
import overviewRoutes from '../../apps/overview/routes'; | |||
import onboardingRoutes from '../../apps/tutorials/routes'; | |||
import organizationsRoutes from '../../apps/organizations/routes'; | |||
import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; | |||
import portfolioRoutes from '../../apps/portfolio/routes'; | |||
@@ -169,12 +170,7 @@ const startReactApp = (lang, currentUser, appState) => { | |||
component={lazyLoad(() => import('../components/extensions/GlobalPageExtension'))} | |||
/> | |||
<Route path="issues" component={IssuesPageSelector} /> | |||
<Route | |||
path="onboarding" | |||
component={lazyLoad(() => | |||
import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') | |||
)} | |||
/> | |||
<Route path="onboarding" childRoutes={onboardingRoutes} /> | |||
<Route path="organizations" childRoutes={organizationsRoutes} /> | |||
<Route path="projects" childRoutes={projectsRoutes} /> | |||
<Route path="quality_gates" childRoutes={qualityGatesRoutes} /> |
@@ -27,7 +27,7 @@ const routes = [ | |||
() => (isSonarCloud() ? import('./sonarcloud/Home') : import('./components/AboutApp')) | |||
) | |||
}, | |||
childRoutes: isSonarCloud | |||
childRoutes: isSonarCloud() | |||
? [ | |||
{ | |||
path: 'contact', |
@@ -34,7 +34,7 @@ export default class UserExternalIdentity extends React.PureComponent { | |||
componentDidUpdate(prevProps) { | |||
if (prevProps.user !== this.props.user) { | |||
this.this.fetchIdentityProviders(); | |||
this.fetchIdentityProviders(); | |||
} | |||
} | |||
@@ -55,7 +55,9 @@ export class NoFavoriteProjects extends React.PureComponent<StateProps> { | |||
<p>{translate('projects.no_favorite_projects.how_to_add_projects')}</p> | |||
<div className="huge-spacer-top"> | |||
<a className="button" href="#" onClick={this.onAnalyzeProjectClick}> | |||
{translate('my_account.analyze_new_project')} | |||
{isSonarCloud() | |||
? translate('provisioning.create_new_project') | |||
: translate('my_account.analyze_new_project')} | |||
</a> | |||
<Dropdown | |||
className="display-inline-block big-spacer-left" |
@@ -50,7 +50,7 @@ exports[`renders for SonarCloud 1`] = ` | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
my_account.analyze_new_project | |||
provisioning.create_new_project | |||
</a> | |||
<Dropdown | |||
className="display-inline-block big-spacer-left" |
@@ -24,10 +24,9 @@ import Header from './Header'; | |||
import Search from './Search'; | |||
import Projects from './Projects'; | |||
import CreateProjectForm from './CreateProjectForm'; | |||
import { PAGE_SIZE, Project } from './utils'; | |||
import ListFooter from '../../components/controls/ListFooter'; | |||
import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; | |||
import { getComponents } from '../../api/components'; | |||
import { getComponents, Project } from '../../api/components'; | |||
import { Organization, Visibility } from '../../app/types'; | |||
import { toNotSoISOString } from '../../helpers/dates'; | |||
import { translate } from '../../helpers/l10n'; | |||
@@ -54,6 +53,8 @@ interface State { | |||
visibility?: Visibility; | |||
} | |||
const PAGE_SIZE = 50; | |||
export default class App extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
@@ -94,19 +95,22 @@ export default class App extends React.PureComponent<Props, State> { | |||
qualifiers: this.state.qualifiers, | |||
visibility: this.state.visibility | |||
}; | |||
getComponents(parameters).then(r => { | |||
if (this.mounted) { | |||
let projects: Project[] = r.components; | |||
if (this.state.page > 1) { | |||
projects = [...this.state.projects, ...projects]; | |||
getComponents(parameters).then( | |||
r => { | |||
if (this.mounted) { | |||
let projects: Project[] = r.components; | |||
if (this.state.page > 1) { | |||
projects = [...this.state.projects, ...projects]; | |||
} | |||
this.setState({ ready: true, projects, selection: [], total: r.paging.total }); | |||
} | |||
this.setState({ ready: true, projects, selection: [], total: r.paging.total }); | |||
} | |||
}); | |||
}, | |||
() => {} | |||
); | |||
}; | |||
loadMore = () => { | |||
this.setState({ ready: false, page: this.state.page + 1 }, this.requestProjects); | |||
this.setState(({ page }) => ({ ready: false, page: page + 1 }), this.requestProjects); | |||
}; | |||
onSearch = (query: string) => { | |||
@@ -152,18 +156,15 @@ export default class App extends React.PureComponent<Props, State> { | |||
this.setState({ ready: false, page: 1, analyzedBefore }, this.requestProjects); | |||
onProjectSelected = (project: string) => { | |||
const newSelection = uniq([...this.state.selection, project]); | |||
this.setState({ selection: newSelection }); | |||
this.setState(({ selection }) => ({ selection: uniq([...selection, project]) })); | |||
}; | |||
onProjectDeselected = (project: string) => { | |||
const newSelection = without(this.state.selection, project); | |||
this.setState({ selection: newSelection }); | |||
this.setState(({ selection }) => ({ selection: without(selection, project) })); | |||
}; | |||
onAllSelected = () => { | |||
const newSelection = this.state.projects.map(project => project.key); | |||
this.setState({ selection: newSelection }); | |||
this.setState(({ projects }) => ({ selection: projects.map(project => project.key) })); | |||
}; | |||
onAllDeselected = () => { |
@@ -20,11 +20,11 @@ | |||
import * as React from 'react'; | |||
import { Link } from 'react-router'; | |||
import ProjectRowActions from './ProjectRowActions'; | |||
import { Project } from './utils'; | |||
import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer'; | |||
import Checkbox from '../../components/controls/Checkbox'; | |||
import QualifierIcon from '../../components/icons-components/QualifierIcon'; | |||
import DateTooltipFormatter from '../../components/intl/DateTooltipFormatter'; | |||
import { Project } from '../../api/components'; | |||
interface Props { | |||
currentUser: { login: string }; |
@@ -19,9 +19,8 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import RestoreAccessModal from './RestoreAccessModal'; | |||
import { Project } from './utils'; | |||
import ApplyTemplate from '../permissions/project/components/ApplyTemplate'; | |||
import { getComponentShow } from '../../api/components'; | |||
import { getComponentShow, Project } from '../../api/components'; | |||
import { getComponentNavigation } from '../../api/nav'; | |||
import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown'; | |||
import { translate } from '../../helpers/l10n'; |
@@ -20,9 +20,9 @@ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import ProjectRow from './ProjectRow'; | |||
import { Project } from './utils'; | |||
import { Organization } from '../../app/types'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { Project } from '../../api/components'; | |||
interface Props { | |||
currentUser: { login: string }; |
@@ -19,11 +19,11 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import { Project } from './utils'; | |||
import { grantPermissionToUser } from '../../api/permissions'; | |||
import Modal from '../../components/controls/Modal'; | |||
import { SubmitButton, ResetButtonLink } from '../../components/ui/buttons'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { Project } from '../../api/components'; | |||
interface Props { | |||
currentUser: { login: string }; |
@@ -21,16 +21,16 @@ import * as React from 'react'; | |||
import { sortBy } from 'lodash'; | |||
import BulkApplyTemplateModal from './BulkApplyTemplateModal'; | |||
import DeleteModal from './DeleteModal'; | |||
import { QUALIFIERS_ORDER, Project } from './utils'; | |||
import { Organization, Visibility } from '../../app/types'; | |||
import Checkbox from '../../components/controls/Checkbox'; | |||
import { translate } from '../../helpers/l10n'; | |||
import QualifierIcon from '../../components/icons-components/QualifierIcon'; | |||
import HelpTooltip from '../../components/controls/HelpTooltip'; | |||
import DateInput from '../../components/controls/DateInput'; | |||
import Select from '../../components/controls/Select'; | |||
import SearchBox from '../../components/controls/SearchBox'; | |||
import { Button } from '../../components/ui/buttons'; | |||
import { Project } from '../../api/components'; | |||
import { Organization, Visibility } from '../../app/types'; | |||
import { translate } from '../../helpers/l10n'; | |||
export interface Props { | |||
analyzedBefore: Date | undefined; | |||
@@ -59,6 +59,8 @@ interface State { | |||
deleteModal: boolean; | |||
} | |||
const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP']; | |||
export default class Search extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { bulkApplyTemplateModal: false, deleteModal: false }; |
@@ -34,8 +34,8 @@ import { getSecurityHotspots } from '../../../api/security-reports'; | |||
import { isLongLivingBranch } from '../../../helpers/branches'; | |||
import DocTooltip from '../../../components/docs/DocTooltip'; | |||
import { getRulesUrl } from '../../../helpers/urls'; | |||
import '../style.css'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import '../style.css'; | |||
interface Props { | |||
branchLike?: BranchLike; | |||
@@ -145,23 +145,24 @@ export default class App extends React.PureComponent<Props, State> { | |||
to={{ pathname: '/documentation/security-reports' }}> | |||
{translate('learn_more')} | |||
</Link> | |||
</div> | |||
<div className="alert alert-info spacer-top"> | |||
<FormattedMessage | |||
defaultMessage={translate('security_reports.info')} | |||
id="security_reports.info" | |||
values={{ | |||
link: ( | |||
<Link | |||
to={getRulesUrl( | |||
{ types: 'SECURITY_HOTSPOT,VULNERABILITY' }, | |||
isSonarCloud() ? component.organization : undefined | |||
)}> | |||
{translate('security_reports.info.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
<p className="alert alert-info spacer-top display-inline-block"> | |||
<FormattedMessage | |||
defaultMessage={translate('security_reports.info')} | |||
id="security_reports.info" | |||
tagName="p" | |||
values={{ | |||
link: ( | |||
<Link | |||
to={getRulesUrl( | |||
{ types: 'SECURITY_HOTSPOT,VULNERABILITY' }, | |||
isSonarCloud() ? component.organization : undefined | |||
)}> | |||
{translate('security_reports.info.link')} | |||
</Link> | |||
) | |||
}} | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div className="display-inline-flex-center"> |
@@ -38,32 +38,33 @@ exports[`handle checkbox for cwe display 1`] = ` | |||
> | |||
learn_more | |||
</Link> | |||
</div> | |||
<div | |||
className="alert alert-info spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
<p | |||
className="alert alert-info spacer-top display-inline-block" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
tagName="p" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
} | |||
} | |||
} | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
} | |||
} | |||
} | |||
/> | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div | |||
@@ -147,32 +148,33 @@ exports[`handle checkbox for cwe display 2`] = ` | |||
> | |||
learn_more | |||
</Link> | |||
</div> | |||
<div | |||
className="alert alert-info spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
<p | |||
className="alert alert-info spacer-top display-inline-block" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
tagName="p" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
} | |||
} | |||
} | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
} | |||
} | |||
} | |||
/> | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div | |||
@@ -299,32 +301,33 @@ exports[`renders owaspTop10 1`] = ` | |||
> | |||
learn_more | |||
</Link> | |||
</div> | |||
<div | |||
className="alert alert-info spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
<p | |||
className="alert alert-info spacer-top display-inline-block" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
tagName="p" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
} | |||
} | |||
} | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
} | |||
} | |||
} | |||
/> | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div | |||
@@ -408,32 +411,33 @@ exports[`renders sansTop25 1`] = ` | |||
> | |||
learn_more | |||
</Link> | |||
</div> | |||
<div | |||
className="alert alert-info spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
<p | |||
className="alert alert-info spacer-top display-inline-block" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
tagName="p" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
} | |||
} | |||
} | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
} | |||
} | |||
} | |||
/> | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div | |||
@@ -517,32 +521,33 @@ exports[`renders with cwe 1`] = ` | |||
> | |||
learn_more | |||
</Link> | |||
</div> | |||
<div | |||
className="alert alert-info spacer-top" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
<p | |||
className="alert alert-info spacer-top display-inline-block" | |||
> | |||
<FormattedMessage | |||
defaultMessage="security_reports.info" | |||
id="security_reports.info" | |||
tagName="p" | |||
values={ | |||
Object { | |||
"link": <Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to={ | |||
Object { | |||
"pathname": "/coding_rules", | |||
"query": Object { | |||
"types": "SECURITY_HOTSPOT,VULNERABILITY", | |||
}, | |||
} | |||
} | |||
} | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
> | |||
security_reports.info.link | |||
</Link>, | |||
} | |||
} | |||
} | |||
/> | |||
/> | |||
</p> | |||
</div> | |||
</header> | |||
<div |
@@ -37,20 +37,12 @@ | |||
margin: var(--gridSize) auto calc(2 * var(--gridSize)); | |||
} | |||
.sonarcloud-oauth-providers.oauth-providers > ul > li { | |||
margin-bottom: var(--gridSize); | |||
} | |||
.sonarcloud-oauth-providers.oauth-providers > ul > li > a > span { | |||
padding-left: calc(1.5 * var(--gridSize)); | |||
.sonarcloud-oauth-providers.oauth-providers > ul { | |||
width: 174px; | |||
} | |||
.sonarcloud-oauth-providers.oauth-providers > ul > li > a > span::before { | |||
content: ''; | |||
border-left: 1px var(--gray71) solid; | |||
height: 10px; | |||
opacity: 0.4; | |||
margin-right: calc(1.5 * var(--gridSize)); | |||
.sonarcloud-oauth-providers.oauth-providers > ul > li { | |||
margin-bottom: var(--gridSize); | |||
} | |||
.sonarcloud-oauth-providers.oauth-providers .oauth-providers-help { |
@@ -18,7 +18,7 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.oauth-providers > ul { | |||
width: 180px; | |||
width: 200px; | |||
margin-left: auto; | |||
margin-right: auto; | |||
} | |||
@@ -28,39 +28,6 @@ | |||
margin-bottom: 30px; | |||
} | |||
.oauth-providers > ul > li > a { | |||
display: block; | |||
width: 180px; | |||
line-height: 22px; | |||
padding: 8px 12px; | |||
border: 1px solid rgba(0, 0, 0, 0.15); | |||
border-radius: 2px; | |||
box-sizing: border-box; | |||
background-color: var(--darkBlue); | |||
color: #fff; | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.oauth-providers > ul > li > a:hover, | |||
.oauth-providers > ul > li > a:focus { | |||
box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.1); | |||
} | |||
.oauth-providers > ul > li > a.dark-text { | |||
color: var(--secondFontColor); | |||
} | |||
.oauth-providers > ul > li > a.dark-text:hover, | |||
.oauth-providers > ul > li > a.dark-text:focus { | |||
box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.1); | |||
} | |||
.oauth-providers > ul > li > a > span { | |||
padding-left: 6px; | |||
} | |||
.oauth-providers-help { | |||
position: absolute; | |||
top: 15px; |
@@ -19,11 +19,11 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import { translateWithParameters } from '../../../helpers/l10n'; | |||
import { IdentityProvider } from '../../../app/types'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import { isDarkColor } from '../../../helpers/colors'; | |||
import IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; | |||
import { translateWithParameters } from '../../../helpers/l10n'; | |||
import { getBaseUrl } from '../../../helpers/urls'; | |||
import { IdentityProvider } from '../../../app/types'; | |||
import './OAuthProviders.css'; | |||
interface Props { | |||
@@ -58,25 +58,16 @@ interface ItemProps { | |||
} | |||
function OAuthProvider({ format, identityProvider, returnTo }: ItemProps) { | |||
const hasDarkBackground = isDarkColor(identityProvider.backgroundColor); | |||
return ( | |||
<li> | |||
<a | |||
className={classNames({ 'dark-text': !hasDarkBackground })} | |||
href={ | |||
<IdentityProviderLink | |||
identityProvider={identityProvider} | |||
url={ | |||
`${getBaseUrl()}/sessions/init/${identityProvider.key}` + | |||
`?return_to=${encodeURIComponent(returnTo)}` | |||
} | |||
style={{ backgroundColor: identityProvider.backgroundColor }}> | |||
<img | |||
alt={identityProvider.name} | |||
height="20" | |||
src={getBaseUrl() + identityProvider.iconPath} | |||
width="20" | |||
/> | |||
}> | |||
<span>{format(identityProvider.name)}</span> | |||
</a> | |||
</IdentityProviderLink> | |||
{identityProvider.helpMessage && ( | |||
<HelpTooltip className="oauth-providers-help" overlay={identityProvider.helpMessage} /> | |||
)} |
@@ -38,49 +38,42 @@ exports[`should render correctly 1`] = ` | |||
exports[`should render correctly 2`] = ` | |||
<li> | |||
<a | |||
className="" | |||
href="/sessions/init/foo?return_to=" | |||
style={ | |||
<IdentityProviderLink | |||
identityProvider={ | |||
Object { | |||
"backgroundColor": "#000", | |||
"iconPath": "/some/path", | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
url="/sessions/init/foo?return_to=" | |||
> | |||
<img | |||
alt="Foo" | |||
height="20" | |||
src="/some/path" | |||
width="20" | |||
/> | |||
<span> | |||
login.login_with_x.Foo | |||
</span> | |||
</a> | |||
</IdentityProviderLink> | |||
</li> | |||
`; | |||
exports[`should render correctly 3`] = ` | |||
<li> | |||
<a | |||
className="" | |||
href="/sessions/init/bar?return_to=" | |||
style={ | |||
<IdentityProviderLink | |||
identityProvider={ | |||
Object { | |||
"backgroundColor": "#00F", | |||
"helpMessage": "Help message!", | |||
"iconPath": "/icon/path", | |||
"key": "bar", | |||
"name": "Bar", | |||
} | |||
} | |||
url="/sessions/init/bar?return_to=" | |||
> | |||
<img | |||
alt="Bar" | |||
height="20" | |||
src="/icon/path" | |||
width="20" | |||
/> | |||
<span> | |||
login.login_with_x.Bar | |||
</span> | |||
</a> | |||
</IdentityProviderLink> | |||
<HelpTooltip | |||
className="oauth-providers-help" | |||
overlay="Help message!" | |||
@@ -90,24 +83,20 @@ exports[`should render correctly 3`] = ` | |||
exports[`should use the custom label formatter 1`] = ` | |||
<li> | |||
<a | |||
className="" | |||
href="/sessions/init/foo?return_to=" | |||
style={ | |||
<IdentityProviderLink | |||
identityProvider={ | |||
Object { | |||
"backgroundColor": "#000", | |||
"iconPath": "/some/path", | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
url="/sessions/init/foo?return_to=" | |||
> | |||
<img | |||
alt="Foo" | |||
height="20" | |||
src="/some/path" | |||
width="20" | |||
/> | |||
<span> | |||
custom_format.Foo | |||
</span> | |||
</a> | |||
</IdentityProviderLink> | |||
</li> | |||
`; |
@@ -18,19 +18,22 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import handleRequiredAuthentication from '../../app/utils/handleRequiredAuthentication'; | |||
import Modal from '../../components/controls/Modal'; | |||
import { ResetButtonLink, Button } from '../../components/ui/buttons'; | |||
import OnboardingPrivateIcon from '../../components/icons-components/OnboardingPrivateIcon'; | |||
import OnboardingProjectIcon from '../../components/icons-components/OnboardingProjectIcon'; | |||
import OnboardingTeamIcon from '../../components/icons-components/OnboardingTeamIcon'; | |||
import { Button, ResetButtonLink } from '../../components/ui/buttons'; | |||
import { translate } from '../../helpers/l10n'; | |||
import { CurrentUser, isLoggedIn } from '../../app/types'; | |||
import { getCurrentUser } from '../../store/rootReducer'; | |||
import './styles.css'; | |||
interface OwnProps { | |||
onFinish: () => void; | |||
onClose: (doSkipOnboarding?: boolean) => void; | |||
onOpenOrganizationOnboarding: () => void; | |||
onOpenProjectOnboarding: () => void; | |||
onOpenTeamOnboarding: () => void; | |||
} | |||
@@ -41,12 +44,25 @@ interface StateProps { | |||
type Props = OwnProps & StateProps; | |||
export class Onboarding extends React.PureComponent<Props> { | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
componentDidMount() { | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
handleRequiredAuthentication(); | |||
} | |||
} | |||
openProjectOnboarding = () => { | |||
this.props.onClose(false); | |||
this.context.router.push('/onboarding'); | |||
}; | |||
onFinish = () => { | |||
this.props.onClose(true); | |||
}; | |||
render() { | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
return null; | |||
@@ -57,41 +73,35 @@ export class Onboarding extends React.PureComponent<Props> { | |||
<Modal | |||
contentLabel={header} | |||
medium={true} | |||
onRequestClose={this.props.onFinish} | |||
onRequestClose={this.onFinish} | |||
shouldCloseOnOverlayClick={false}> | |||
<header className="modal-head"> | |||
<h2>{header}</h2> | |||
</header> | |||
<div className="modal-body"> | |||
<p className="spacer-top big-spacer-bottom"> | |||
{translate('onboarding.header.description')} | |||
</p> | |||
<ul className="onboarding-choices"> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom">{translate('onboarding.analyze_public_code')}</p> | |||
<Button onClick={this.props.onOpenProjectOnboarding}> | |||
{translate('onboarding.analyze_public_code.button')} | |||
</Button> | |||
</li> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom">{translate('onboarding.analyze_private_code')}</p> | |||
<Button onClick={this.props.onOpenOrganizationOnboarding}> | |||
{translate('onboarding.analyze_private_code.button')} | |||
</Button> | |||
</li> | |||
<li className="text-center"> | |||
<p className="big-spacer-bottom"> | |||
{translate('onboarding.contribute_existing_project')} | |||
</p> | |||
<Button onClick={this.props.onOpenTeamOnboarding}> | |||
{translate('onboarding.contribute_existing_project.button')} | |||
</Button> | |||
</li> | |||
</ul> | |||
<div className="modal-simple-head text-center"> | |||
<h1>{translate('onboarding.header')}</h1> | |||
<p className="spacer-top">{translate('onboarding.header.description')}</p> | |||
</div> | |||
<div className="modal-simple-body text-center onboarding-choices"> | |||
<Button className="onboarding-choice" onClick={this.openProjectOnboarding}> | |||
<OnboardingProjectIcon /> | |||
<span>{translate('onboarding.analyze_public_code')}</span> | |||
<p className="note">{translate('onboarding.analyze_public_code.note')}</p> | |||
</Button> | |||
<Button className="onboarding-choice" onClick={this.props.onOpenOrganizationOnboarding}> | |||
<OnboardingPrivateIcon /> | |||
<span>{translate('onboarding.analyze_private_code')}</span> | |||
<p className="note">{translate('onboarding.analyze_private_code.note')}</p> | |||
</Button> | |||
<Button className="onboarding-choice" onClick={this.props.onOpenTeamOnboarding}> | |||
<OnboardingTeamIcon /> | |||
<span>{translate('onboarding.contribute_existing_project')}</span> | |||
<p className="note">{translate('onboarding.contribute_existing_project.note')}</p> | |||
</Button> | |||
</div> | |||
<div className="modal-simple-footer text-center"> | |||
<ResetButtonLink className="spacer-bottom" onClick={this.onFinish}> | |||
{translate('not_now')} | |||
</ResetButtonLink> | |||
<p className="note">{translate('onboarding.footer')}</p> | |||
</div> | |||
<footer className="modal-foot"> | |||
<ResetButtonLink onClick={this.props.onFinish}>{translate('close')}</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
); | |||
} |
@@ -27,9 +27,8 @@ it('renders correctly', () => { | |||
shallow( | |||
<Onboarding | |||
currentUser={{ isLoggedIn: true }} | |||
onFinish={jest.fn()} | |||
onClose={jest.fn()} | |||
onOpenOrganizationOnboarding={jest.fn()} | |||
onOpenProjectOnboarding={jest.fn()} | |||
onOpenTeamOnboarding={jest.fn()} | |||
/> | |||
) | |||
@@ -37,25 +36,25 @@ it('renders correctly', () => { | |||
}); | |||
it('should correctly open the different tutorials', () => { | |||
const onFinish = jest.fn(); | |||
const onClose = jest.fn(); | |||
const onOpenOrganizationOnboarding = jest.fn(); | |||
const onOpenProjectOnboarding = jest.fn(); | |||
const onOpenTeamOnboarding = jest.fn(); | |||
const push = jest.fn(); | |||
const wrapper = shallow( | |||
<Onboarding | |||
currentUser={{ isLoggedIn: true }} | |||
onFinish={onFinish} | |||
onClose={onClose} | |||
onOpenOrganizationOnboarding={onOpenOrganizationOnboarding} | |||
onOpenProjectOnboarding={onOpenProjectOnboarding} | |||
onOpenTeamOnboarding={onOpenTeamOnboarding} | |||
/> | |||
/>, | |||
{ context: { router: { push } } } | |||
); | |||
click(wrapper.find('ResetButtonLink')); | |||
expect(onFinish).toHaveBeenCalled(); | |||
expect(onClose).toHaveBeenCalled(); | |||
wrapper.find('Button').forEach(button => click(button)); | |||
expect(onOpenOrganizationOnboarding).toHaveBeenCalled(); | |||
expect(onOpenProjectOnboarding).toHaveBeenCalled(); | |||
expect(onOpenTeamOnboarding).toHaveBeenCalled(); | |||
expect(push).toHaveBeenCalledWith('/onboarding'); | |||
}); |
@@ -4,79 +4,81 @@ exports[`renders correctly 1`] = ` | |||
<Modal | |||
contentLabel="onboarding.header" | |||
medium={true} | |||
onRequestClose={[MockFunction]} | |||
onRequestClose={[Function]} | |||
shouldCloseOnOverlayClick={false} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
onboarding.header | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body" | |||
className="modal-simple-head text-center" | |||
> | |||
<h1> | |||
onboarding.header | |||
</h1> | |||
<p | |||
className="spacer-top big-spacer-bottom" | |||
className="spacer-top" | |||
> | |||
onboarding.header.description | |||
</p> | |||
<ul | |||
className="onboarding-choices" | |||
</div> | |||
<div | |||
className="modal-simple-body text-center onboarding-choices" | |||
> | |||
<Button | |||
className="onboarding-choice" | |||
onClick={[Function]} | |||
> | |||
<li | |||
className="text-center" | |||
<OnboardingProjectIcon /> | |||
<span> | |||
onboarding.analyze_public_code | |||
</span> | |||
<p | |||
className="note" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.analyze_public_code | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.analyze_public_code.button | |||
</Button> | |||
</li> | |||
<li | |||
className="text-center" | |||
onboarding.analyze_public_code.note | |||
</p> | |||
</Button> | |||
<Button | |||
className="onboarding-choice" | |||
onClick={[MockFunction]} | |||
> | |||
<OnboardingPrivateIcon /> | |||
<span> | |||
onboarding.analyze_private_code | |||
</span> | |||
<p | |||
className="note" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.analyze_private_code | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.analyze_private_code.button | |||
</Button> | |||
</li> | |||
<li | |||
className="text-center" | |||
onboarding.analyze_private_code.note | |||
</p> | |||
</Button> | |||
<Button | |||
className="onboarding-choice" | |||
onClick={[MockFunction]} | |||
> | |||
<OnboardingTeamIcon /> | |||
<span> | |||
onboarding.contribute_existing_project | |||
</span> | |||
<p | |||
className="note" | |||
> | |||
<p | |||
className="big-spacer-bottom" | |||
> | |||
onboarding.contribute_existing_project | |||
</p> | |||
<Button | |||
onClick={[MockFunction]} | |||
> | |||
onboarding.contribute_existing_project.button | |||
</Button> | |||
</li> | |||
</ul> | |||
onboarding.contribute_existing_project.note | |||
</p> | |||
</Button> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
<div | |||
className="modal-simple-footer text-center" | |||
> | |||
<ResetButtonLink | |||
onClick={[MockFunction]} | |||
className="spacer-bottom" | |||
onClick={[Function]} | |||
> | |||
close | |||
not_now | |||
</ResetButtonLink> | |||
</footer> | |||
<p | |||
className="note" | |||
> | |||
onboarding.footer | |||
</p> | |||
</div> | |||
</Modal> | |||
`; |
@@ -0,0 +1,132 @@ | |||
/* | |||
* 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 DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; | |||
import { getIdentityProviders } from '../../../api/users'; | |||
import { getRepositories } from '../../../api/alm-integration'; | |||
import { translateWithParameters } from '../../../helpers/l10n'; | |||
import { IdentityProvider, LoggedInUser } from '../../../app/types'; | |||
interface Props { | |||
currentUser: LoggedInUser; | |||
} | |||
interface State { | |||
identityProviders: IdentityProvider[]; | |||
installationUrl?: string; | |||
installed?: boolean; | |||
loading: boolean; | |||
} | |||
export default class AutoProjectCreate extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
state: State = { identityProviders: [], loading: true }; | |||
componentDidMount() { | |||
this.mounted = true; | |||
Promise.all([this.fetchIdentityProviders(), this.fetchRepositories()]).then( | |||
this.stopLoading, | |||
this.stopLoading | |||
); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
fetchIdentityProviders = () => { | |||
return getIdentityProviders().then( | |||
({ identityProviders }) => { | |||
if (this.mounted) { | |||
this.setState({ identityProviders }); | |||
} | |||
}, | |||
() => { | |||
return Promise.resolve(); | |||
} | |||
); | |||
}; | |||
fetchRepositories = () => { | |||
return getRepositories().then(({ installation }) => { | |||
if (this.mounted) { | |||
this.setState({ | |||
installationUrl: installation.installationUrl, | |||
installed: installation.enabled | |||
}); | |||
} | |||
}); | |||
}; | |||
stopLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
render() { | |||
if (this.state.loading) { | |||
return <DeferredSpinner />; | |||
} | |||
const { currentUser } = this.props; | |||
const identityProvider = this.state.identityProviders.find( | |||
identityProvider => identityProvider.key === currentUser.externalProvider | |||
); | |||
if (!identityProvider) { | |||
return null; | |||
} | |||
return ( | |||
<> | |||
<p className="alert alert-info width-60 big-spacer-bottom"> | |||
{translateWithParameters( | |||
'onboarding.create_project.beta_feature_x', | |||
identityProvider.name | |||
)} | |||
</p> | |||
{this.state.installed ? ( | |||
'Repositories list' | |||
) : ( | |||
<div> | |||
<p className="spacer-bottom"> | |||
{translateWithParameters( | |||
'onboarding.create_project.install_app_x', | |||
identityProvider.name | |||
)} | |||
</p> | |||
<IdentityProviderLink | |||
className="display-inline-block" | |||
identityProvider={identityProvider} | |||
small={true} | |||
url={this.state.installationUrl}> | |||
{translateWithParameters( | |||
'onboarding.create_project.install_app_x.button', | |||
identityProvider.name | |||
)} | |||
</IdentityProviderLink> | |||
</div> | |||
)} | |||
</> | |||
); | |||
} | |||
} |
@@ -0,0 +1,182 @@ | |||
/* | |||
* 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 classNames from 'classnames'; | |||
import * as PropTypes from 'prop-types'; | |||
import { connect } from 'react-redux'; | |||
import Helmet from 'react-helmet'; | |||
import AutoProjectCreate from './AutoProjectCreate'; | |||
import ManualProjectCreate from './ManualProjectCreate'; | |||
import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; | |||
import { getCurrentUser } from '../../../store/rootReducer'; | |||
import { skipOnboarding } from '../../../store/users/actions'; | |||
import { CurrentUser, isLoggedIn } from '../../../app/types'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { ProjectBase } from '../../../api/components'; | |||
import { getProjectUrl, getOrganizationUrl } from '../../../helpers/urls'; | |||
import '../../../app/styles/sonarcloud.css'; | |||
import '../styles.css'; | |||
interface OwnProps { | |||
onFinishOnboarding: () => void; | |||
} | |||
interface StateProps { | |||
currentUser: CurrentUser; | |||
} | |||
interface DispatchProps { | |||
skipOnboarding: () => void; | |||
} | |||
enum Tabs { | |||
AUTO, | |||
MANUAL | |||
} | |||
type Props = OwnProps & StateProps & DispatchProps; | |||
interface State { | |||
activeTab: Tabs; | |||
} | |||
export class CreateProjectOnboarding extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
static contextTypes = { | |||
router: PropTypes.object | |||
}; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { activeTab: this.shouldDisplayTabs(props) ? Tabs.AUTO : Tabs.MANUAL }; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
if (!isLoggedIn(this.props.currentUser)) { | |||
handleRequiredAuthentication(); | |||
} | |||
document.body.classList.add('white-page'); | |||
document.documentElement.classList.add('white-page'); | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
document.body.classList.remove('white-page'); | |||
document.documentElement.classList.remove('white-page'); | |||
} | |||
handleProjectCreate = (projects: Pick<ProjectBase, 'key'>[], organization?: string) => { | |||
if (projects.length > 1 && organization) { | |||
this.context.router.push(getOrganizationUrl(organization) + '/projects'); | |||
} else if (projects.length === 1) { | |||
this.context.router.push(getProjectUrl(projects[0].key)); | |||
} | |||
}; | |||
shouldDisplayTabs = ({ currentUser } = this.props) => { | |||
return ( | |||
isLoggedIn(currentUser) && | |||
['bitbucket', 'github'].includes(currentUser.externalProvider || '') | |||
); | |||
}; | |||
showAuto = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ activeTab: Tabs.AUTO }); | |||
}; | |||
showManual = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
this.setState({ activeTab: Tabs.MANUAL }); | |||
}; | |||
render() { | |||
const { currentUser } = this.props; | |||
if (!isLoggedIn(currentUser)) { | |||
return null; | |||
} | |||
const { activeTab } = this.state; | |||
const header = translate('onboarding.create_project.header'); | |||
return ( | |||
<> | |||
<Helmet title={header} titleTemplate="%s" /> | |||
<div className="sonarcloud page page-limited"> | |||
<div className="page-header"> | |||
<h1 className="page-title">{header}</h1> | |||
</div> | |||
{this.shouldDisplayTabs() && ( | |||
<ul className="flex-tabs"> | |||
<li> | |||
<a | |||
className={classNames('js-auto', { selected: activeTab === Tabs.AUTO })} | |||
href="#" | |||
onClick={this.showAuto}> | |||
{translate('onboarding.create_project.select_repositories')} | |||
<span | |||
className={classNames( | |||
'rounded alert alert-small spacer-left display-inline-block', | |||
{ | |||
'alert-info': activeTab === Tabs.AUTO, | |||
'alert-muted': activeTab !== Tabs.AUTO | |||
} | |||
)}> | |||
{translate('beta')} | |||
</span> | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
className={classNames('js-manual', { selected: activeTab === Tabs.MANUAL })} | |||
href="#" | |||
onClick={this.showManual}> | |||
{translate('onboarding.create_project.create_manually')} | |||
</a> | |||
</li> | |||
</ul> | |||
)} | |||
{activeTab === Tabs.AUTO ? ( | |||
<AutoProjectCreate currentUser={currentUser} /> | |||
) : ( | |||
<ManualProjectCreate | |||
currentUser={currentUser} | |||
onProjectCreate={this.handleProjectCreate} | |||
/> | |||
)} | |||
</div> | |||
</> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: any): StateProps => { | |||
return { | |||
currentUser: getCurrentUser(state) | |||
}; | |||
}; | |||
const mapDispatchToProps: DispatchProps = { skipOnboarding }; | |||
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | |||
CreateProjectOnboarding | |||
); |
@@ -0,0 +1,227 @@ | |||
/* | |||
* 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 { sortBy } from 'lodash'; | |||
import { connect } from 'react-redux'; | |||
import CreateOrganizationForm from '../../account/organizations/CreateOrganizationForm'; | |||
import Select from '../../../components/controls/Select'; | |||
import { Button, SubmitButton } from '../../../components/ui/buttons'; | |||
import { LoggedInUser, Organization } from '../../../app/types'; | |||
import { fetchMyOrganizations } from '../../account/organizations/actions'; | |||
import { getMyOrganizations } from '../../../store/rootReducer'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { createProject, ProjectBase } from '../../../api/components'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
interface StateProps { | |||
userOrganizations: Organization[]; | |||
} | |||
interface DispatchProps { | |||
fetchMyOrganizations: () => Promise<void>; | |||
} | |||
interface OwnProps { | |||
currentUser: LoggedInUser; | |||
onProjectCreate: (project: ProjectBase[]) => void; | |||
} | |||
type Props = OwnProps & StateProps & DispatchProps; | |||
interface State { | |||
createOrganizationModal: boolean; | |||
projectName: string; | |||
projectKey: string; | |||
selectedOrganization: string; | |||
submitting: boolean; | |||
} | |||
export class ManualProjectCreate extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
createOrganizationModal: false, | |||
projectName: '', | |||
projectKey: '', | |||
selectedOrganization: | |||
props.userOrganizations.length <= 1 ? props.userOrganizations[0].key : '', | |||
submitting: false | |||
}; | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
} | |||
componentWillUnmount() { | |||
this.mounted = false; | |||
} | |||
closeCreateOrganization = () => { | |||
this.setState({ createOrganizationModal: false }); | |||
}; | |||
handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { | |||
event.preventDefault(); | |||
if (this.isValid()) { | |||
const { projectKey, projectName, selectedOrganization } = this.state; | |||
this.setState({ submitting: true }); | |||
createProject({ | |||
project: projectKey, | |||
name: projectName, | |||
organization: selectedOrganization | |||
}).then( | |||
({ project }) => this.props.onProjectCreate([project]), | |||
() => { | |||
if (this.mounted) { | |||
this.setState({ submitting: false }); | |||
} | |||
} | |||
); | |||
} | |||
}; | |||
handleOrganizationSelect = ({ value }: { value: string }) => { | |||
this.setState({ selectedOrganization: value }); | |||
}; | |||
handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ projectName: event.currentTarget.value }); | |||
}; | |||
handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ projectKey: event.currentTarget.value }); | |||
}; | |||
isValid = () => { | |||
const { projectKey, projectName, selectedOrganization } = this.state; | |||
return Boolean(projectKey && projectName && selectedOrganization); | |||
}; | |||
onCreateOrganization = (organization: { key: string }) => { | |||
this.props.fetchMyOrganizations().then( | |||
() => { | |||
this.handleOrganizationSelect({ value: organization.key }); | |||
this.closeCreateOrganization(); | |||
}, | |||
() => { | |||
this.closeCreateOrganization(); | |||
} | |||
); | |||
}; | |||
showCreateOrganization = () => { | |||
this.setState({ createOrganizationModal: true }); | |||
}; | |||
render() { | |||
const { submitting } = this.state; | |||
return ( | |||
<> | |||
<form onSubmit={this.handleFormSubmit}> | |||
<div className="form-field"> | |||
<label htmlFor="select-organization"> | |||
{translate('onboarding.create_project.organization')} | |||
<em className="mandatory">*</em> | |||
</label> | |||
<Select | |||
autoFocus={true} | |||
className="input-super-large" | |||
clearable={false} | |||
id="select-organization" | |||
onChange={this.handleOrganizationSelect} | |||
options={sortBy(this.props.userOrganizations, o => o.name.toLowerCase()).map( | |||
organization => ({ | |||
label: organization.name, | |||
value: organization.key | |||
}) | |||
)} | |||
required={true} | |||
value={this.state.selectedOrganization} | |||
/> | |||
<Button | |||
className="button-link big-spacer-left js-new-org" | |||
onClick={this.showCreateOrganization}> | |||
{translate('onboarding.create_project.create_new_org')} | |||
</Button> | |||
</div> | |||
<div className="form-field"> | |||
<label htmlFor="project-name"> | |||
{translate('onboarding.create_project.project_name')} | |||
<em className="mandatory">*</em> | |||
</label> | |||
<input | |||
className="input-super-large" | |||
id="project-name" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={this.handleProjectNameChange} | |||
required={true} | |||
type="text" | |||
value={this.state.projectName} | |||
/> | |||
</div> | |||
<div className="form-field"> | |||
<label htmlFor="project-key"> | |||
{translate('onboarding.create_project.project_key')} | |||
<em className="mandatory">*</em> | |||
</label> | |||
<input | |||
className="input-super-large" | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={this.handleProjectKeyChange} | |||
required={true} | |||
type="text" | |||
value={this.state.projectKey} | |||
/> | |||
</div> | |||
<SubmitButton disabled={!this.isValid() || submitting}> | |||
{translate('onboarding.create_project.create_project')} | |||
</SubmitButton> | |||
<DeferredSpinner className="spacer-left" loading={submitting} /> | |||
</form> | |||
{this.state.createOrganizationModal && ( | |||
<CreateOrganizationForm | |||
onClose={this.closeCreateOrganization} | |||
onCreate={this.onCreateOrganization} | |||
/> | |||
)} | |||
</> | |||
); | |||
} | |||
} | |||
const mapDispatchToProps = ({ | |||
fetchMyOrganizations | |||
} as any) as DispatchProps; | |||
const mapStateToProps = (state: any): StateProps => { | |||
return { | |||
userOrganizations: getMyOrganizations(state) | |||
}; | |||
}; | |||
export default connect<StateProps, DispatchProps, OwnProps>(mapStateToProps, mapDispatchToProps)( | |||
ManualProjectCreate | |||
); |
@@ -0,0 +1,69 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import AutoProjectCreate from '../AutoProjectCreate'; | |||
import { getIdentityProviders } from '../../../../api/users'; | |||
import { getRepositories } from '../../../../api/alm-integration'; | |||
import { LoggedInUser } from '../../../../app/types'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
jest.mock('../../../../api/users', () => ({ | |||
getIdentityProviders: jest.fn().mockResolvedValue({ | |||
identityProviders: [ | |||
{ | |||
backgroundColor: 'blue', | |||
iconPath: 'icon/path', | |||
key: 'foo', | |||
name: 'Foo Provider' | |||
} | |||
] | |||
}) | |||
})); | |||
jest.mock('../../../../api/alm-integration', () => ({ | |||
getRepositories: jest.fn().mockResolvedValue({ | |||
installation: { | |||
installationUrl: 'https://alm.foo.com/install', | |||
enabled: false | |||
} | |||
}) | |||
})); | |||
const user: LoggedInUser = { isLoggedIn: true, login: 'foo', name: 'Foo', externalProvider: 'foo' }; | |||
beforeEach(() => { | |||
(getIdentityProviders as jest.Mock<any>).mockClear(); | |||
(getRepositories as jest.Mock<any>).mockClear(); | |||
}); | |||
it('should display the provider app install button', async () => { | |||
const wrapper = getWrapper(); | |||
expect(wrapper).toMatchSnapshot(); | |||
expect(getIdentityProviders).toHaveBeenCalled(); | |||
expect(getRepositories).toHaveBeenCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow(<AutoProjectCreate currentUser={user} {...props} />); | |||
} |
@@ -0,0 +1,58 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { CreateProjectOnboarding } from '../CreateProjectOnboarding'; | |||
import { LoggedInUser } from '../../../../app/types'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
const user: LoggedInUser = { | |||
externalProvider: 'github', | |||
isLoggedIn: true, | |||
login: 'foo', | |||
name: 'Foo' | |||
}; | |||
it('should render correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should render with Manual creation only', () => { | |||
expect(getWrapper({ currentUser: { ...user, externalProvider: 'vsts' } })).toMatchSnapshot(); | |||
}); | |||
it('should switch tabs', () => { | |||
const wrapper = getWrapper(); | |||
click(wrapper.find('.js-manual')); | |||
expect(wrapper.find('Connect(ManualProjectCreate)').exists()).toBeTruthy(); | |||
click(wrapper.find('.js-auto')); | |||
expect(wrapper.find('AutoProjectCreate').exists()).toBeTruthy(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<CreateProjectOnboarding | |||
currentUser={user} | |||
onFinishOnboarding={jest.fn()} | |||
skipOnboarding={jest.fn()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,79 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import { ManualProjectCreate } from '../ManualProjectCreate'; | |||
import { change, click, submit, waitAndUpdate } from '../../../../helpers/testUtils'; | |||
import { createProject } from '../../../../api/components'; | |||
jest.mock('../../../../api/components', () => ({ | |||
createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } }) | |||
})); | |||
beforeEach(() => { | |||
(createProject as jest.Mock<any>).mockClear(); | |||
}); | |||
it('should render correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should allow to create a new org', async () => { | |||
const fetchMyOrganizations = jest.fn().mockResolvedValueOnce([]); | |||
const wrapper = getWrapper({ fetchMyOrganizations }); | |||
click(wrapper.find('.js-new-org')); | |||
const createForm = wrapper.find('Connect(CreateOrganizationForm)'); | |||
expect(createForm.exists()).toBeTruthy(); | |||
createForm.prop<Function>('onCreate')({ key: 'baz' }); | |||
expect(fetchMyOrganizations).toHaveBeenCalled(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state('selectedOrganization')).toBe('baz'); | |||
}); | |||
it('should correctly create a project', async () => { | |||
const onProjectCreate = jest.fn(); | |||
const wrapper = getWrapper({ onProjectCreate }); | |||
wrapper.find('Select').prop<Function>('onChange')({ value: 'foo' }); | |||
change(wrapper.find('#project-name'), 'Bar'); | |||
expect(wrapper.find('SubmitButton')).toMatchSnapshot(); | |||
change(wrapper.find('#project-key'), 'bar'); | |||
expect(wrapper.find('SubmitButton')).toMatchSnapshot(); | |||
submit(wrapper.find('form')); | |||
expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' }); | |||
await waitAndUpdate(wrapper); | |||
expect(onProjectCreate).toBeCalledWith([{ key: 'bar', name: 'Bar' }]); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<ManualProjectCreate | |||
currentUser={{ isLoggedIn: true, login: 'foo', name: 'Foo' }} | |||
fetchMyOrganizations={jest.fn()} | |||
onProjectCreate={jest.fn()} | |||
userOrganizations={[{ key: 'foo', name: 'Foo' }, { key: 'bar', name: 'Bar' }]} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,39 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display the provider app install button 1`] = ` | |||
<DeferredSpinner | |||
timeout={100} | |||
/> | |||
`; | |||
exports[`should display the provider app install button 2`] = ` | |||
<React.Fragment> | |||
<p | |||
className="alert alert-info width-60 big-spacer-bottom" | |||
> | |||
onboarding.create_project.beta_feature_x.Foo Provider | |||
</p> | |||
<div> | |||
<p | |||
className="spacer-bottom" | |||
> | |||
onboarding.create_project.install_app_x.Foo Provider | |||
</p> | |||
<IdentityProviderLink | |||
className="display-inline-block" | |||
identityProvider={ | |||
Object { | |||
"backgroundColor": "blue", | |||
"iconPath": "icon/path", | |||
"key": "foo", | |||
"name": "Foo Provider", | |||
} | |||
} | |||
small={true} | |||
url="https://alm.foo.com/install" | |||
> | |||
onboarding.create_project.install_app_x.button.Foo Provider | |||
</IdentityProviderLink> | |||
</div> | |||
</React.Fragment> | |||
`; |
@@ -0,0 +1,97 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.create_project.header" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="sonarcloud page page-limited" | |||
> | |||
<div | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
onboarding.create_project.header | |||
</h1> | |||
</div> | |||
<ul | |||
className="flex-tabs" | |||
> | |||
<li> | |||
<a | |||
className="js-auto selected" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
onboarding.create_project.select_repositories | |||
<span | |||
className="rounded alert alert-small spacer-left display-inline-block alert-info" | |||
> | |||
beta | |||
</span> | |||
</a> | |||
</li> | |||
<li> | |||
<a | |||
className="js-manual" | |||
href="#" | |||
onClick={[Function]} | |||
> | |||
onboarding.create_project.create_manually | |||
</a> | |||
</li> | |||
</ul> | |||
<AutoProjectCreate | |||
currentUser={ | |||
Object { | |||
"externalProvider": "github", | |||
"isLoggedIn": true, | |||
"login": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
/> | |||
</div> | |||
</React.Fragment> | |||
`; | |||
exports[`should render with Manual creation only 1`] = ` | |||
<React.Fragment> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="onboarding.create_project.header" | |||
titleTemplate="%s" | |||
/> | |||
<div | |||
className="sonarcloud page page-limited" | |||
> | |||
<div | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
onboarding.create_project.header | |||
</h1> | |||
</div> | |||
<Connect(ManualProjectCreate) | |||
currentUser={ | |||
Object { | |||
"externalProvider": "vsts", | |||
"isLoggedIn": true, | |||
"login": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
onProjectCreate={[Function]} | |||
/> | |||
</div> | |||
</React.Fragment> | |||
`; |
@@ -0,0 +1,125 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly create a project 1`] = ` | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
onboarding.create_project.create_project | |||
</SubmitButton> | |||
`; | |||
exports[`should correctly create a project 2`] = ` | |||
<SubmitButton | |||
disabled={false} | |||
> | |||
onboarding.create_project.create_project | |||
</SubmitButton> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<React.Fragment> | |||
<form | |||
onSubmit={[Function]} | |||
> | |||
<div | |||
className="form-field" | |||
> | |||
<label | |||
htmlFor="select-organization" | |||
> | |||
onboarding.create_project.organization | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<Select | |||
autoFocus={true} | |||
className="input-super-large" | |||
clearable={false} | |||
id="select-organization" | |||
onChange={[Function]} | |||
options={ | |||
Array [ | |||
Object { | |||
"label": "Bar", | |||
"value": "bar", | |||
}, | |||
Object { | |||
"label": "Foo", | |||
"value": "foo", | |||
}, | |||
] | |||
} | |||
required={true} | |||
value="" | |||
/> | |||
<Button | |||
className="button-link big-spacer-left js-new-org" | |||
onClick={[Function]} | |||
> | |||
onboarding.create_project.create_new_org | |||
</Button> | |||
</div> | |||
<div | |||
className="form-field" | |||
> | |||
<label | |||
htmlFor="project-name" | |||
> | |||
onboarding.create_project.project_name | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<input | |||
className="input-super-large" | |||
id="project-name" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={[Function]} | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="form-field" | |||
> | |||
<label | |||
htmlFor="project-key" | |||
> | |||
onboarding.create_project.project_key | |||
<em | |||
className="mandatory" | |||
> | |||
* | |||
</em> | |||
</label> | |||
<input | |||
className="input-super-large" | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onChange={[Function]} | |||
required={true} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
<SubmitButton | |||
disabled={true} | |||
> | |||
onboarding.create_project.create_project | |||
</SubmitButton> | |||
<DeferredSpinner | |||
className="spacer-left" | |||
loading={false} | |||
timeout={100} | |||
/> | |||
</form> | |||
</React.Fragment> | |||
`; |
@@ -0,0 +1,36 @@ | |||
/* | |||
* 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 { lazyLoad } from '../../components/lazyLoad'; | |||
import { isSonarCloud } from '../../helpers/system'; | |||
const routes = [ | |||
{ | |||
indexRoute: { | |||
component: lazyLoad( | |||
() => | |||
isSonarCloud() | |||
? import('../../apps/tutorials/createProjectOnboarding/CreateProjectOnboarding') | |||
: import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') | |||
) | |||
} | |||
} | |||
]; | |||
export default routes; |
@@ -58,5 +58,43 @@ | |||
.onboarding-choices { | |||
display: flex; | |||
justify-content: space-around; | |||
padding: 24px 0 44px; | |||
padding-top: 44px; | |||
padding-bottom: 44px; | |||
background-color: var(--barBackgroundColor); | |||
} | |||
.onboarding-choice { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: flex-end; | |||
padding: calc(2 * var(--gridSize)); | |||
width: 190px; | |||
height: 190px; | |||
background-color: #fff; | |||
border: solid 1px #fff; | |||
border-radius: 3px; | |||
transition: all 0.2s ease; | |||
box-shadow: 0 1px 1px 1px var(--barBorderColor); | |||
} | |||
.onboarding-choice svg { | |||
color: var(--gray40); | |||
margin-bottom: calc(3 * var(--gridSize)); | |||
} | |||
.onboarding-choice span { | |||
font-size: var(--mediumFontSize); | |||
margin-bottom: calc(var(--gridSize) / 2); | |||
} | |||
.onboarding-choice .note { | |||
font-weight: 400; | |||
} | |||
.onboarding-choice:hover, | |||
.onboarding-choice:focus, | |||
.onboarding-choice:active { | |||
background-color: #fff; | |||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.35); | |||
color: var(--darkBlue); | |||
} |
@@ -172,6 +172,7 @@ | |||
margin: 0; | |||
outline: none; | |||
padding: 0; | |||
box-shadow: none; | |||
-webkit-appearance: none; | |||
} | |||
@@ -0,0 +1,50 @@ | |||
/* | |||
* 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 Icon, { IconProps } from './Icon'; | |||
export default function OnboardingPrivateIcon({ | |||
className, | |||
fill = 'currentColor', | |||
size | |||
}: IconProps) { | |||
return ( | |||
<Icon className={className} size={size || 64} viewBox=""> | |||
<g fill="none" stroke={fill} strokeWidth="2"> | |||
<path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" /> | |||
<path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" /> | |||
<path d="M58 31V17H6v22" /> | |||
<path | |||
d="M50 36c0-9.389-7.611-17-17-17s-17 7.611-17 17 7.611 17 17 17 17-7.611 17-17" | |||
fill="#FFF" | |||
stroke="none" | |||
/> | |||
<path d="M50 36c0-9.389-7.611-17-17-17s-17 7.611-17 17 7.611 17 17 17 17-7.611 17-17z" /> | |||
<mask fill="#FFF" id="a"> | |||
<path d="M0 56h62V0H0z" /> | |||
</mask> | |||
<path | |||
d="M27 45h12V33H27zm10-12v-4c0-1.023-.391-2.047-1.172-2.828C35.048 25.391 34.023 25 33 25s-2.048.391-2.828 1.172C29.391 26.953 29 27.977 29 29v4" | |||
mask="url(#a)" | |||
/> | |||
</g> | |||
</Icon> | |||
); | |||
} |
@@ -0,0 +1,37 @@ | |||
/* | |||
* 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 Icon, { IconProps } from './Icon'; | |||
export default function OnboardingProjectIcon({ | |||
className, | |||
fill = 'currentColor', | |||
size | |||
}: IconProps) { | |||
return ( | |||
<Icon className={className} size={size || 64} viewBox=""> | |||
<g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2"> | |||
<path d="M2 59h60V13H2zm0-46h60V5H2zm3-4h2m2 0h2m2 0h2m2 0h42" /> | |||
<path d="M59 34h-6l-2-4h-6l-2 5h-6l-2 2h-6l-2-4h-6l-2 5h-6l-2 4H5m1 14v-9m4 9v-6m4 6V43m4 13V45m4 11V42m4 14V39m4 17V41m4 15V46m4 10V40m4 16V44m4 12V37m4 19V38m4 18V43m4 13V39m-3-18h-2m-2 0h-2m-2 0h-2M9 29h14M9 33h7m17-12h8m-14 4h8m-8-4h4m-21 4h12v-4H10z" /> | |||
<path d="M58 31V17H6v22" /> | |||
</g> | |||
</Icon> | |||
); | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* 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 Icon, { IconProps } from './Icon'; | |||
export default function OnboardingTeamIcon({ className, fill = 'currentColor', size }: IconProps) { | |||
return ( | |||
<Icon className={className} size={size || 64} viewBox=""> | |||
<g fill="none" fillRule="evenodd" stroke={fill} strokeWidth="2"> | |||
<path d="M32 9v5M11.5195 43.0898l7.48-4.091m33.481-18.0994l-7.48 4.1m-33.481-4.1l7.48 4.1M45 38.999l7.48 4.101M32 50v5m15-23c0 8.284-6.715 15-15 15s-15-6.716-15-15c0-8.285 6.715-15 15-15s15 6.715 15 15z" /> | |||
<path d="M40 38c0 1.656-3.58 2-8 2s-8-.344-8-2m16 0v-3l-5-3-1-1m-10 7v-3l5-3 1-1m6-4c0 2.2-1.8 4-4 4s-4-1.8-4-4v-1c0-2.2 1.8-4 4-4s4 1.8 4 4v1zm-.0098-21.71c7.18 1.069 13.439 4.96 17.609 10.51m-17.609 42.91c7.18-1.07 13.439-4.96 17.609-10.51M6.6299 41.25c-1.06-2.88-1.63-6-1.63-9.25s.57-6.37 1.63-9.25m3.7705-6.9502c4.17-5.55 10.43-9.44 17.609-10.51m-17.609 42.9104c4.17 5.55 10.43 9.439 17.609 10.51M57.3701 22.75c1.06 2.88 1.63 6 1.63 9.25s-.57 6.37-1.63 9.25" /> | |||
<path d="M36 5c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M12 19c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2m51 0c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M12 45c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2m51 0c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2M36 59c0 2.209-1.79 4-4 4-2.209 0-4-1.791-4-4 0-2.21 1.791-4 4-4 2.21 0 4 1.79 4 4zm-5 0h2" /> | |||
</g> | |||
</Icon> | |||
); | |||
} |
@@ -0,0 +1,68 @@ | |||
/* | |||
* 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. | |||
*/ | |||
a.identity-provider-link { | |||
display: block; | |||
width: auto; | |||
line-height: 22px; | |||
padding: var(--gridSize) calc(1.5 * var(--gridSize)); | |||
border: 1px solid rgba(0, 0, 0, 0.15); | |||
border-radius: 2px; | |||
box-sizing: border-box; | |||
background-color: var(--darkBlue); | |||
color: #fff; | |||
white-space: nowrap; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
a.identity-provider-link.small { | |||
line-height: 14px; | |||
padding: calc(var(--gridSize) / 2) var(--gridSize); | |||
} | |||
a.identity-provider-link:hover, | |||
a.identity-provider-link:focus { | |||
box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.1); | |||
} | |||
a.identity-provider-link.dark-text { | |||
color: var(--secondFontColor); | |||
} | |||
a.identity-provider-link.dark-text:hover, | |||
a.identity-provider-link.dark-text:focus { | |||
box-shadow: inset 0 0 0 100px rgba(0, 0, 0, 0.1); | |||
} | |||
a.identity-provider-link > img { | |||
padding-right: calc(1.5 * var(--gridSize)); | |||
} | |||
a.identity-provider-link.small > img { | |||
padding-right: var(--gridSize); | |||
} | |||
a.identity-provider-link > span::before { | |||
content: ''; | |||
opacity: 0.4; | |||
border-left: 1px var(--gray71) solid; | |||
margin-right: calc(1.5 * var(--gridSize)); | |||
} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* 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 classNames from 'classnames'; | |||
import { isDarkColor } from '../../helpers/colors'; | |||
import { getBaseUrl } from '../../helpers/urls'; | |||
import { IdentityProvider } from '../../app/types'; | |||
import './IdentityProviderLink.css'; | |||
interface Props { | |||
children: React.ReactNode; | |||
className?: string; | |||
identityProvider: IdentityProvider; | |||
small?: boolean; | |||
url: string | undefined; | |||
} | |||
export default function IdentityProviderLink({ | |||
children, | |||
className, | |||
identityProvider, | |||
small, | |||
url | |||
}: Props) { | |||
const size = small ? 14 : 20; | |||
return ( | |||
<a | |||
className={classNames( | |||
'identity-provider-link', | |||
{ 'dark-text': !isDarkColor(identityProvider.backgroundColor), small }, | |||
className | |||
)} | |||
href={url} | |||
style={{ backgroundColor: identityProvider.backgroundColor }}> | |||
<img | |||
alt={identityProvider.name} | |||
height={size} | |||
src={getBaseUrl() + identityProvider.iconPath} | |||
width={size} | |||
/> | |||
{children} | |||
</a> | |||
); | |||
} |
@@ -0,0 +1,39 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import IdentityProviderLink from '../IdentityProviderLink'; | |||
const identityProvider = { | |||
backgroundColor: '#000', | |||
iconPath: '/some/path', | |||
key: 'foo', | |||
name: 'Foo' | |||
}; | |||
it('should render correctly', () => { | |||
expect( | |||
shallow( | |||
<IdentityProviderLink identityProvider={identityProvider} url="/url/foo/bar"> | |||
Link text | |||
</IdentityProviderLink> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -0,0 +1,21 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<a | |||
className="identity-provider-link" | |||
href="/url/foo/bar" | |||
style={ | |||
Object { | |||
"backgroundColor": "#000", | |||
} | |||
} | |||
> | |||
<img | |||
alt="Foo" | |||
height={20} | |||
src="/some/path" | |||
width={20} | |||
/> | |||
Link text | |||
</a> | |||
`; |
@@ -21,6 +21,7 @@ back=Back | |||
backup=Backup | |||
backup_verb=Back up | |||
best=Best | |||
beta=BETA | |||
blocker=Blocker | |||
bold=Bold | |||
branch=Branch | |||
@@ -108,6 +109,7 @@ never=Never | |||
new_name=New name | |||
none=None | |||
no_tags=No tags | |||
not_now=Not now | |||
off=Off | |||
on=On | |||
organization_key=Organization Key | |||
@@ -1492,12 +1494,13 @@ my_account.create_new_organization=Create new organization | |||
# PROJECT PROVISIONING | |||
# | |||
#------------------------------------------------------------------------------ | |||
provisioning.create_new_project=Create new project | |||
provisioning.no_analysis=No analysis has been performed since creation. The only available section is the configuration. | |||
provisioning.no_analysis.delete=Either you should retry analysis or simply {0}. | |||
provisioning.no_analysis.delete_project=delete the project | |||
provisioning.no_analysis_on_main_branch={branch} has not been analyzed yet. | |||
provisioning.only_provisioned=Only Provisioned | |||
provisioning.only_provisioned.tooltip=Provisioned projects are projects that have been created, but have not been analyzed yet. | |||
provisioning.no_analysis_on_main_branch={branch} has not been analyzed yet. | |||
#------------------------------------------------------------------------------ | |||
@@ -2649,23 +2652,37 @@ footer.web_api=Web API | |||
# ONBOARDING | |||
# | |||
#------------------------------------------------------------------------------ | |||
onboarding.header=Welcome to SonarCloud! | |||
onboarding.header.description=Let us help you get started. What do you want to do? | |||
onboarding.header=Welcome to SonarCloud | |||
onboarding.header.description=Let us help you get started in your journey to code quality | |||
onboarding.footer=Don't worry you can do all of this later. Just click the "+" icon on your top bar. | |||
onboarding.project.header=Analyze a project | |||
onboarding.project.header.description=Want to quickly analyze a first project? Follow these {0} easy steps. | |||
onboarding.create_project.header=Create project(s) | |||
onboarding.create_project.beta_feature_x=This feature is being beta tested. We offer to create projects from your {0} repositories only for public personal projects on your personal SonarCloud organization. For other kind of projects please create them maually. | |||
onboarding.create_project.create_manually=Create manually | |||
onboarding.create_project.create_new_org=I want to create another organization | |||
onboarding.create_project.create_project=Create project | |||
onboarding.create_project.create_projects=Create projects | |||
onboarding.create_project.install_app_x=We need you to install the Sonarcloud {0} application in order to select which repositories you want to analyze. | |||
onboarding.create_project.install_app_x.button=Install SonarCloud {0} application | |||
onboarding.create_project.organization=Organization | |||
onboarding.create_project.project_key=Project key | |||
onboarding.create_project.project_name=Project name | |||
onboarding.create_project.select_repositories=Select repositories | |||
onboarding.team.header=Join a team | |||
onboarding.team.first_step=Well congrats, the first step is done! | |||
onboarding.team.how_to_join=To join a team, the only thing you need to do is to be a user registered on Sonarcloud. The administrator of the Sonarcloud organization you wish to join has to add you to his organization's members {link}. Ask him to do so! | |||
onboarding.team.work_in_progress=We are currently working on a better way to join a team or invite people to yours. | |||
onboarding.analyze_public_code=I want to analyze public code | |||
onboarding.analyze_public_code.button=Analyze a project | |||
onboarding.analyze_private_code=I want to analyze private code | |||
onboarding.analyze_private_code.button=Setup a new organization | |||
onboarding.contribute_existing_project=I want to contribute to an existing project | |||
onboarding.contribute_existing_project.button=Join a team | |||
onboarding.analyze_public_code.note=Free | |||
onboarding.analyze_public_code=Analyze public code | |||
onboarding.analyze_private_code=Analyze private code | |||
onboarding.analyze_private_code.note=From 10$ / month | |||
onboarding.contribute_existing_project=Join a team | |||
onboarding.contribute_existing_project.note=Free | |||
onboarding.token.header=Provide a token | |||
onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point of time in your user account. |