From b08814f7807c1443592af65cd68c2a51dfd4ee37 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Fri, 20 Jul 2018 16:57:23 +0200 Subject: [PATCH] SONAR-11036 Install integration with GitHub or BitBucket Cloud * 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 integration --- server/sonar-web/scripts/start.js | 4 +- .../utils.ts => api/alm-integration.ts} | 16 +- .../sonar-web/src/main/js/api/components.ts | 29 ++- .../main/js/app/components/StartupModal.tsx | 16 +- .../components/nav/global/GlobalNavPlus.tsx | 2 +- .../__snapshots__/GlobalNavPlus-test.tsx.snap | 2 +- .../main/js/app/styles/components/alerts.css | 13 +- .../main/js/app/styles/components/modals.css | 18 ++ .../src/main/js/app/styles/init/forms.css | 12 + .../src/main/js/app/styles/sonarcloud.css | 67 +++++ server/sonar-web/src/main/js/app/theme.js | 1 + server/sonar-web/src/main/js/app/types.ts | 2 + .../src/main/js/app/utils/startReactApp.js | 8 +- .../src/main/js/apps/about/routes.ts | 2 +- .../account/profile/UserExternalIdentity.js | 2 +- .../components/NoFavoriteProjects.tsx | 4 +- .../NoFavoriteProjects-test.tsx.snap | 2 +- .../main/js/apps/projectsManagement/App.tsx | 35 +-- .../js/apps/projectsManagement/ProjectRow.tsx | 2 +- .../projectsManagement/ProjectRowActions.tsx | 3 +- .../js/apps/projectsManagement/Projects.tsx | 2 +- .../projectsManagement/RestoreAccessModal.tsx | 2 +- .../js/apps/projectsManagement/Search.tsx | 8 +- .../apps/securityReports/components/App.tsx | 37 +-- .../__tests__/__snapshots__/App-test.tsx.snap | 245 +++++++++--------- .../sessions/components/LoginSonarCloud.css | 16 +- .../sessions/components/OAuthProviders.css | 35 +-- .../sessions/components/OAuthProviders.tsx | 25 +- .../OAuthProviders-test.tsx.snap | 55 ++-- .../src/main/js/apps/tutorials/Onboarding.tsx | 82 +++--- .../tutorials/__tests__/Onboarding-test.tsx | 17 +- .../__snapshots__/Onboarding-test.tsx.snap | 116 +++++---- .../AutoProjectCreate.tsx | 132 ++++++++++ .../CreateProjectOnboarding.tsx | 182 +++++++++++++ .../ManualProjectCreate.tsx | 227 ++++++++++++++++ .../__tests__/AutoProjectCreate-test.tsx | 69 +++++ .../CreateProjectOnboarding-test.tsx | 58 +++++ .../__tests__/ManualProjectCreate-test.tsx | 79 ++++++ .../AutoProjectCreate-test.tsx.snap | 39 +++ .../CreateProjectOnboarding-test.tsx.snap | 97 +++++++ .../ManualProjectCreate-test.tsx.snap | 125 +++++++++ .../src/main/js/apps/tutorials/routes.ts | 36 +++ .../src/main/js/apps/tutorials/styles.css | 40 ++- .../js/components/controls/react-select.css | 1 + .../OnboardingPrivateIcon.tsx | 50 ++++ .../OnboardingProjectIcon.tsx | 37 +++ .../icons-components/OnboardingTeamIcon.tsx | 33 +++ .../js/components/ui/IdentityProviderLink.css | 68 +++++ .../js/components/ui/IdentityProviderLink.tsx | 62 +++++ .../__tests__/IdentityProviderLink-test.tsx | 39 +++ .../IdentityProviderLink-test.tsx.snap | 21 ++ .../resources/org/sonar/l10n/core.properties | 35 ++- 52 files changed, 1897 insertions(+), 413 deletions(-) rename server/sonar-web/src/main/js/{apps/projectsManagement/utils.ts => api/alm-integration.ts} (72%) create mode 100644 server/sonar-web/src/main/js/app/styles/sonarcloud.css create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/tutorials/routes.ts create mode 100644 server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx create mode 100644 server/sonar-web/src/main/js/components/ui/IdentityProviderLink.css create mode 100644 server/sonar-web/src/main/js/components/ui/IdentityProviderLink.tsx create mode 100644 server/sonar-web/src/main/js/components/ui/__tests__/IdentityProviderLink-test.tsx create mode 100644 server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/IdentityProviderLink-test.tsx.snap diff --git a/server/sonar-web/scripts/start.js b/server/sonar-web/scripts/start.js index ce3726d43e9..b1f4d4da3ca 100644 --- a/server/sonar-web/scripts/start.js +++ b/server/sonar-web/scripts/start.js @@ -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 } } }); diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts b/server/sonar-web/src/main/js/api/alm-integration.ts similarity index 72% rename from server/sonar-web/src/main/js/apps/projectsManagement/utils.ts rename to server/sonar-web/src/main/js/api/alm-integration.ts index 183b47bc732..0232632ccc2 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/utils.ts +++ b/server/sonar-web/src/main/js/api/alm-integration.ts @@ -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); +} diff --git a/server/sonar-web/src/main/js/api/components.ts b/server/sonar-web/src/main/js/api/components.ts index a069c38b17e..c917f8a4460 100644 --- a/server/sonar-web/src/main/js/api/components.ts +++ b/server/sonar-web/src/main/js/api/components.ts @@ -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 { +): 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 { +}): Promise<{ project: ProjectBase }> { return postJSON('/api/projects/create', data).catch(throwGlobalError); } diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index bbd6c574eec..c49a77d2052 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -99,11 +99,13 @@ export class StartupModal extends React.PureComponent { 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 { 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 { {modal === ModalKey.license && } {modal === ModalKey.onboarding && ( )} diff --git a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx index 412846db466..58030362c11 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/global/GlobalNavPlus.tsx @@ -69,7 +69,7 @@ export default class GlobalNavPlus extends React.PureComponent {
  • - {translate('my_account.analyze_new_project')} + {translate('provisioning.create_new_project')}
  • diff --git a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavPlus-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavPlus-test.tsx.snap index 1664c15d58c..56a23c2dc5e 100644 --- a/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavPlus-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavPlus-test.tsx.snap @@ -12,7 +12,7 @@ exports[`render 1`] = ` href="#" onClick={[Function]} > - my_account.analyze_new_project + provisioning.create_new_project
  • 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); +} diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 91ba51aa4c3..0a90beef99b 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -64,6 +64,7 @@ module.exports = { smallFontSize: '12px', mediumFontSize: '14px', bigFontSize: '16px', + hugeFontSize: '24px', controlHeight: `${3 * grid}px`, smallControlHeight: `${2.5 * grid}px`, diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index e02ced80d7e..4f1b00c3f76 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -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; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.js b/server/sonar-web/src/main/js/app/utils/startReactApp.js index 69602ff1e13..788cd86620a 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.js +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.js @@ -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'))} /> - - import('../../apps/tutorials/projectOnboarding/ProjectOnboardingPage') - )} - /> + diff --git a/server/sonar-web/src/main/js/apps/about/routes.ts b/server/sonar-web/src/main/js/apps/about/routes.ts index 315b604da61..c41f05acfc2 100644 --- a/server/sonar-web/src/main/js/apps/about/routes.ts +++ b/server/sonar-web/src/main/js/apps/about/routes.ts @@ -27,7 +27,7 @@ const routes = [ () => (isSonarCloud() ? import('./sonarcloud/Home') : import('./components/AboutApp')) ) }, - childRoutes: isSonarCloud + childRoutes: isSonarCloud() ? [ { path: 'contact', diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js index 89e595feed4..2148e50098f 100644 --- a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js +++ b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.js @@ -34,7 +34,7 @@ export default class UserExternalIdentity extends React.PureComponent { componentDidUpdate(prevProps) { if (prevProps.user !== this.props.user) { - this.this.fetchIdentityProviders(); + this.fetchIdentityProviders(); } } diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index 4cd7b7d2164..c5727397933 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -55,7 +55,9 @@ export class NoFavoriteProjects extends React.PureComponent {

    {translate('projects.no_favorite_projects.how_to_add_projects')}

    - {translate('my_account.analyze_new_project')} + {isSonarCloud() + ? translate('provisioning.create_new_project') + : translate('my_account.analyze_new_project')} - my_account.analyze_new_project + provisioning.create_new_project { mounted = false; @@ -94,19 +95,22 @@ export default class App extends React.PureComponent { 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 { 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 = () => { diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx index 8853af4a079..7f1b12c3e4d 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx @@ -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 }; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx index 438db5c8a77..5932a17e570 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx @@ -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'; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx index 7e72fb9b057..09add750d94 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx @@ -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 }; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx index 57183554451..7c402913a25 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx @@ -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 }; diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx index 205526b58aa..f336153d976 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx @@ -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 { mounted = false; state: State = { bulkApplyTemplateModal: false, deleteModal: false }; diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx index fa966581a94..def1019b4b4 100755 --- a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx @@ -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 { to={{ pathname: '/documentation/security-reports' }}> {translate('learn_more')} -
    -
    - - {translate('security_reports.info.link')} - - ) - }} - /> +

    + + {translate('security_reports.info.link')} + + ) + }} + /> +

    diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap index b787a7c99a8..86b8d8c2b57 100644 --- a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap @@ -38,32 +38,33 @@ exports[`handle checkbox for cwe display 1`] = ` > learn_more -
    -
    - + - security_reports.info.link - , + > + security_reports.info.link + , + } } - } - /> + /> +

    learn_more -
    -
    - + - security_reports.info.link - , + > + security_reports.info.link + , + } } - } - /> + /> +

    learn_more -
    -
    - + - security_reports.info.link - , + > + security_reports.info.link + , + } } - } - /> + /> +

    learn_more -
    -
    - + - security_reports.info.link - , + > + security_reports.info.link + , + } } - } - /> + /> +

    learn_more -
    -
    - + - security_reports.info.link - , + > + security_reports.info.link + , + } } - } - /> + /> +

    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 { diff --git a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css index 696beeb9cdc..050aca6bc4f 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css +++ b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.css @@ -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; diff --git a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx index 1ecf27de26e..80ad700666e 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx +++ b/server/sonar-web/src/main/js/apps/sessions/components/OAuthProviders.tsx @@ -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 (
  • - - {identityProvider.name} + }> {format(identityProvider.name)} - + {identityProvider.helpMessage && ( )} diff --git a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/OAuthProviders-test.tsx.snap b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/OAuthProviders-test.tsx.snap index 7ab0639e474..e8da4c505fe 100644 --- a/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/OAuthProviders-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/sessions/components/__tests__/__snapshots__/OAuthProviders-test.tsx.snap @@ -38,49 +38,42 @@ exports[`should render correctly 1`] = ` exports[`should render correctly 2`] = `
  • - - Foo login.login_with_x.Foo - +
  • `; exports[`should render correctly 3`] = `
  • - - Bar login.login_with_x.Bar - + - - Foo custom_format.Foo - +
  • `; diff --git a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx index e6a6d887bd3..e7edd09e99f 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/Onboarding.tsx @@ -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 { + 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 { -
    -

    {header}

    -
    -
    -

    - {translate('onboarding.header.description')} -

    -
      -
    • -

      {translate('onboarding.analyze_public_code')}

      - -
    • -
    • -

      {translate('onboarding.analyze_private_code')}

      - -
    • -
    • -

      - {translate('onboarding.contribute_existing_project')} -

      - -
    • -
    +
    +

    {translate('onboarding.header')}

    +

    {translate('onboarding.header.description')}

    +
    +
    + + + +
    +
    + + {translate('not_now')} + +

    {translate('onboarding.footer')}

    -
    - {translate('close')} -
    ); } diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx index c9b3e77500e..d0350d43cc0 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx @@ -27,9 +27,8 @@ it('renders correctly', () => { shallow( ) @@ -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( + />, + { 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'); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap index e017f778601..e0a56e3fc79 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap @@ -4,79 +4,81 @@ exports[`renders correctly 1`] = ` -
    -

    - onboarding.header -

    -
    +

    + onboarding.header +

    onboarding.header.description

    -
      +
      + - -
    • + + -
    • -
    • + + -
    • -
    + onboarding.contribute_existing_project.note +

    +
    -
    - close + not_now -
    +

    + onboarding.footer +

    +
    `; diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx new file mode 100644 index 00000000000..58c86e34696 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AutoProjectCreate.tsx @@ -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 { + 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 ; + } + + const { currentUser } = this.props; + const identityProvider = this.state.identityProviders.find( + identityProvider => identityProvider.key === currentUser.externalProvider + ); + + if (!identityProvider) { + return null; + } + + return ( + <> +

    + {translateWithParameters( + 'onboarding.create_project.beta_feature_x', + identityProvider.name + )} +

    + {this.state.installed ? ( + 'Repositories list' + ) : ( +
    +

    + {translateWithParameters( + 'onboarding.create_project.install_app_x', + identityProvider.name + )} +

    + + {translateWithParameters( + 'onboarding.create_project.install_app_x.button', + identityProvider.name + )} + +
    + )} + + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx new file mode 100644 index 00000000000..7803335143c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/CreateProjectOnboarding.tsx @@ -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 { + 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[], 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) => { + event.preventDefault(); + this.setState({ activeTab: Tabs.AUTO }); + }; + + showManual = (event: React.MouseEvent) => { + 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 ( + <> + +
    +
    +

    {header}

    +
    + + {this.shouldDisplayTabs() && ( + + )} + + {activeTab === Tabs.AUTO ? ( + + ) : ( + + )} +
    + + ); + } +} + +const mapStateToProps = (state: any): StateProps => { + return { + currentUser: getCurrentUser(state) + }; +}; + +const mapDispatchToProps: DispatchProps = { skipOnboarding }; + +export default connect(mapStateToProps, mapDispatchToProps)( + CreateProjectOnboarding +); diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx new file mode 100644 index 00000000000..59d615583c7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/ManualProjectCreate.tsx @@ -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; +} + +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 { + 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) => { + 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) => { + this.setState({ projectName: event.currentTarget.value }); + }; + + handleProjectKeyChange = (event: React.ChangeEvent) => { + 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 ( + <> +
    +
    + + +
    +
    + + +
    + + {translate('onboarding.create_project.create_project')} + + + + {this.state.createOrganizationModal && ( + + )} + + ); + } +} + +const mapDispatchToProps = ({ + fetchMyOrganizations +} as any) as DispatchProps; + +const mapStateToProps = (state: any): StateProps => { + return { + userOrganizations: getMyOrganizations(state) + }; +}; +export default connect(mapStateToProps, mapDispatchToProps)( + ManualProjectCreate +); diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx new file mode 100644 index 00000000000..10ea4c7e215 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AutoProjectCreate-test.tsx @@ -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).mockClear(); + (getRepositories as jest.Mock).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(); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx new file mode 100644 index 00000000000..f7cfa3ce8da --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/CreateProjectOnboarding-test.tsx @@ -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( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx new file mode 100644 index 00000000000..b79b4e4ae35 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/ManualProjectCreate-test.tsx @@ -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).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('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('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( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap new file mode 100644 index 00000000000..9320a251046 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the provider app install button 1`] = ` + +`; + +exports[`should display the provider app install button 2`] = ` + +

    + onboarding.create_project.beta_feature_x.Foo Provider +

    +
    +

    + onboarding.create_project.install_app_x.Foo Provider +

    + + onboarding.create_project.install_app_x.button.Foo Provider + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap new file mode 100644 index 00000000000..9542df43fb9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/CreateProjectOnboarding-test.tsx.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + + + +`; + +exports[`should render with Manual creation only 1`] = ` + + +
    +
    +

    + onboarding.create_project.header +

    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap new file mode 100644 index 00000000000..fafb751c9bb --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap @@ -0,0 +1,125 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should correctly create a project 1`] = ` + + onboarding.create_project.create_project + +`; + +exports[`should correctly create a project 2`] = ` + + onboarding.create_project.create_project + +`; + +exports[`should render correctly 1`] = ` + +
    +
    + + +
    +
    + + +
    + + onboarding.create_project.create_project + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/tutorials/routes.ts b/server/sonar-web/src/main/js/apps/tutorials/routes.ts new file mode 100644 index 00000000000..9f5b34ba428 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/tutorials/routes.ts @@ -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; diff --git a/server/sonar-web/src/main/js/apps/tutorials/styles.css b/server/sonar-web/src/main/js/apps/tutorials/styles.css index f73428e0f40..798fce21bcc 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/styles.css +++ b/server/sonar-web/src/main/js/apps/tutorials/styles.css @@ -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); } diff --git a/server/sonar-web/src/main/js/components/controls/react-select.css b/server/sonar-web/src/main/js/components/controls/react-select.css index b65548be61e..5cfcff66eae 100644 --- a/server/sonar-web/src/main/js/components/controls/react-select.css +++ b/server/sonar-web/src/main/js/components/controls/react-select.css @@ -172,6 +172,7 @@ margin: 0; outline: none; padding: 0; + box-shadow: none; -webkit-appearance: none; } diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx new file mode 100644 index 00000000000..3592d9b0be0 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingPrivateIcon.tsx @@ -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 ( + + + + + + + + + + + + + + ); +} diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx new file mode 100644 index 00000000000..d2090eed867 --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingProjectIcon.tsx @@ -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 ( + + + + + + + + ); +} diff --git a/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx new file mode 100644 index 00000000000..6ce74838a0d --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/OnboardingTeamIcon.tsx @@ -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 ( + + + + + + + + ); +} diff --git a/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.css b/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.css new file mode 100644 index 00000000000..8277e368f2b --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.css @@ -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)); +} diff --git a/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.tsx b/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.tsx new file mode 100644 index 00000000000..9e4f87c5ed3 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/IdentityProviderLink.tsx @@ -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 ( + + {identityProvider.name} + {children} + + ); +} diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/IdentityProviderLink-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/IdentityProviderLink-test.tsx new file mode 100644 index 00000000000..1a072dd00c4 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/IdentityProviderLink-test.tsx @@ -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( + + Link text + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/IdentityProviderLink-test.tsx.snap b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/IdentityProviderLink-test.tsx.snap new file mode 100644 index 00000000000..d3a38a52376 --- /dev/null +++ b/server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/IdentityProviderLink-test.tsx.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + Foo + Link text + +`; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 881127b4524..954cfa39d18 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -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. -- 2.39.5