allMarkdownRemark {
edges {
node {
+ frontmatter {
+ scope
+ }
fields {
slug
}
}
`).then(result => {
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
- createPage({
- path: node.fields.slug,
- component: path.resolve('./src/templates/page.js'),
- context: {
- // Data passed to context is available in page queries as GraphQL variables.
- slug: node.fields.slug
- }
- });
+ if (node.frontmatter.scope !== 'sonarcloud') {
+ createPage({
+ path: node.fields.slug,
+ component: path.resolve('./src/templates/page.js'),
+ context: {
+ // Data passed to context is available in page queries as GraphQL variables.
+ slug: node.fields.slug
+ }
+ });
+ }
});
resolve();
});
}
],
"marketplace": [],
+ "organization_members": [
+ {
+ "link": "/documentation/organizations/manage-team",
+ "text": "Manage a Team",
+ "scope": "sonarcloud"
+ }
+ ],
+ "organization_projects": [
+ {
+ "link": "/documentation/organizations/index",
+ "text": "Organizations",
+ "scope": "sonarcloud"
+ }
+ ],
"overview": [
{
"link": "/documentation/fixing-the-water-leak",
title: Frequently Asked Branches Questions
---
+<!-- sonarqube -->
+
_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
+<!-- /sonarqube -->
+
**Q:** How long are branches retained?
**A:** Long-lived branches are retained until you delete them manually (**Administration > Branches**).
Short-lived branches are deleted automatically after 30 days with no analysis.
title: Branches
---
+<!-- sonarqube -->
+
_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
+<!-- /sonarqube -->
+
Branch analysis allows you to
* analyze long-lived branches
title: Long-lived Branches
---
+<!-- sonarqube -->
+
_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
+<!-- /sonarqube -->
+
## Status vs Quality Gate
The same quality gate that is applied to the project as a whole is automatically applied to long-lived branches as well. This is not editable.
title: Short-lived Branches
---
+<!-- sonarqube -->
+
_Branch analysis is available as part of [Developer Edition](https://redirect.sonarsource.com/editions/developer.html)_
+<!-- /sonarqube -->
+
## Status vs Quality Gate
For short-lived branches, there is a kind of hard-coded quality gate focusing only on new issues. Its status is reflected by the green|red signal associated with each short-lived branch:
--- /dev/null
+---
+title: Organizations
+scope: sonarcloud
+---
+
+## Overview
+
+An organization is a space where a team or a whole company can collaborate across many projects.
+
+An organization consists of:
+* Projects, on which users collaborate
+* [Members](/organizations/manage-team), who can have different persmissions on the projects
+* [Quality Profiles](/quality-profiles) and [Quality Gates](/quality-gates), which can be customized and shared accross projects
+
+There are 2 kind of organizations:
+* **Personal organizations**. Each account has a personal organization linked to it. This is typically where open-source developers host their personal projects. It is not possible to delete this kind of organization.
+* **Standard organization**. This is the kind of organization that users want to create for their companies or for their open-source communities. As soon as you want to collaborate, it is a good idea to create such an organization.
+
+Organizations can be on:
+* **Free plan**. This is the default plan. Every project in an organization on the free plan is public.
+* **Paid plan**. This plan unlocks the ability to have private projects. Go to the "Billing" page of your organization to upgrade it to the paid plan.
--- /dev/null
+---
+title: Manage a Team
+scope: sonarcloud
+---
+
+Members can collaborate on the projects in the organizations to which they belong. Depending on their permisssions within the organization, members can:
+* Analyse projects
+* Manage project settings (permissions, visibility, quality profiles, ...)
+* Update issues
+* Manage quality gates and quality profiles
+* Administer the organization itself
+
+## Adding Members
+
+Adding members is done on the "Members" page of the organization, and this can be done only by an administrator of
+the organization.
+
+Adding a user as a member is possible only if that user has already signed up on SonarCloud. If the user never authenticated to
+the system, the administrator will simply not be able to find the user in the search modal window.
+
+## Granting permissions
+
+Once added, a user can be granted permissions to perform various operations in the organization. It is up to the
+administrator who added the user to make sure that she gets the relevant permissions.
+
+Organization admins will prefer to create groups to manage permissions, and add new users to those
+groups through the "Members" page. With such an approach, they won't have to manage individal permissions at
+project level for instance.
+
+## Future evolutions
+
+Future versions of SonarCloud will make this onboarding process easier thanks to better integrations with GitHub,
+Bitbucket Cloud and VSTS: users won't have to sign up prior to joining an organization, and their permissions will
+be retrieved at best from the ones existing on the other systems.
export default ({ data }) => {
const page = data.markdownRemark;
- const htmlWithInclusions = page.html.replace(/\<p\>@include (.*)\<\/p\>/, (_, path) => {
- const chunk = data.allMarkdownRemark.edges.find(edge => edge.node.fields.slug === path);
- return chunk ? chunk.node.html : '';
- });
+ const htmlWithInclusions = cutSonarCloudContent(page.html).replace(
+ /\<p\>@include (.*)\<\/p\>/,
+ (_, path) => {
+ const chunk = data.allMarkdownRemark.edges.find(edge => edge.node.fields.slug === path);
+ return chunk ? chunk.node.html : '';
+ }
+ );
return (
<div css={{ paddingTop: 24, paddingBottom: 24 }}>
}
}
`;
+
+function cutSonarCloudContent(content) {
+ const beginning = '<!-- sonarcloud -->';
+ const ending = '<!-- /sonarcloud -->';
+
+ let newContent = content;
+ let start = newContent.indexOf(beginning);
+ let end = newContent.indexOf(ending);
+ while (start !== -1 && end !== -1) {
+ newContent = newContent.substring(0, start) + newContent.substring(end + ending.length);
+ start = newContent.indexOf(beginning);
+ end = newContent.indexOf(ending);
+ }
+
+ return newContent;
+}
--- /dev/null
+Add new members to this organization and manage their permissions. Note that users must have signed up on the service to be able to find and add them.
+
+---
+
+See also: [Manage a Team](/organizations/manage-team)
name: path.basename(file).slice(0, -3),
relativeName: file.slice(0, -3),
title: headerData.title || file,
- order: headerData.order || -1
+ order: headerData.order || -1,
+ scope: headerData.scope && headerData.scope.toLowerCase()
};
})
.sort(compare);
*/
import * as React from 'react';
import * as PropTypes from 'prop-types';
-//eslint-disable-next-line import/no-extraneous-dependencies
+// eslint-disable-next-line import/no-extraneous-dependencies
import * as suggestionsJson from 'Docs/EmbedDocsSuggestions.json';
import { SuggestionsContext } from './SuggestionsContext';
export interface SuggestionLink {
- text: string;
link: string;
+ scope?: 'sonarcloud';
+ text: string;
}
interface SuggestionsJson {
- [key: string]: Array<SuggestionLink>;
+ [key: string]: SuggestionLink[];
}
interface Props {
- children: ({ suggestions }: { suggestions: Array<SuggestionLink> }) => React.ReactNode;
+ children: ({ suggestions }: { suggestions: SuggestionLink[] }) => React.ReactNode;
}
interface State {
- suggestions: Array<SuggestionLink>;
+ suggestions: SuggestionLink[];
}
export default class SuggestionsProvider extends React.Component<Props, State> {
suggestions: PropTypes.object
};
- state = { suggestions: [] };
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool
+ };
+
+ state: State = { suggestions: [] };
getChildContext = (): { suggestions: SuggestionsContext } => {
return {
fetchSuggestions = () => {
const jsonList = suggestionsJson as SuggestionsJson;
- let suggestions: Array<SuggestionLink> = [];
+ let suggestions: SuggestionLink[] = [];
this.keys.forEach(key => {
if (jsonList[key]) {
suggestions = [...jsonList[key], ...suggestions];
};
render() {
- return this.props.children({ suggestions: this.state.suggestions });
+ const suggestions = this.context.onSonarCloud
+ ? this.state.suggestions
+ : this.state.suggestions.filter(suggestion => suggestion.scope !== 'sonarcloud');
+
+ return this.props.children({ suggestions });
}
}
--- /dev/null
+/*
+ * 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 SuggestionsProvider from '../SuggestionsProvider';
+
+jest.mock(
+ 'Docs/EmbedDocsSuggestions.json',
+ () => ({
+ pageA: [{ link: '/foo', text: 'Foo' }, { link: '/bar', text: 'Bar', scope: 'sonarcloud' }],
+ pageB: [{ link: '/qux', text: 'Qux' }]
+ }),
+ { virtual: true }
+);
+
+it('should add & remove suggestions', () => {
+ const children = jest.fn();
+ const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>);
+ const instance = wrapper.instance() as SuggestionsProvider;
+ expect(children).lastCalledWith({ suggestions: [] });
+
+ instance.addSuggestions('pageA');
+ expect(children).lastCalledWith({ suggestions: [{ link: '/foo', text: 'Foo' }] });
+
+ instance.addSuggestions('pageB');
+ expect(children).lastCalledWith({
+ suggestions: [{ link: '/qux', text: 'Qux' }, { link: '/foo', text: 'Foo' }]
+ });
+
+ instance.removeSuggestions('pageA');
+ expect(children).lastCalledWith({ suggestions: [{ link: '/qux', text: 'Qux' }] });
+});
+
+it('should show sonarcloud pages', () => {
+ const children = jest.fn();
+ const wrapper = shallow(<SuggestionsProvider>{children}</SuggestionsProvider>, {
+ context: { onSonarCloud: true }
+ });
+ const instance = wrapper.instance() as SuggestionsProvider;
+ expect(children).lastCalledWith({ suggestions: [] });
+
+ instance.addSuggestions('pageA');
+ expect(children).lastCalledWith({
+ suggestions: [{ link: '/foo', text: 'Foo' }, { link: '/bar', text: 'Bar', scope: 'sonarcloud' }]
+ });
+});
import * as matter from 'gray-matter';
import Helmet from 'react-helmet';
import { Link } from 'react-router';
+import * as PropTypes from 'prop-types';
import Menu from './Menu';
import NotFound from '../../../app/components/NotFound';
import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper';
export default class App extends React.PureComponent<Props, State> {
mounted = false;
+
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool
+ };
+
state: State = { loading: false, notFound: false };
componentDidMount() {
import(`Docs/pages/${path === '' ? 'index' : path}.md`).then(
({ default: content }) => {
if (this.mounted) {
- this.setState({ content, loading: false, notFound: false });
+ const parsed = matter(content || '');
+ if (parsed.data.scope === 'sonarcloud' && !this.context.onSonarCloud) {
+ this.setState({ loading: false, notFound: true });
+ } else {
+ this.setState({ content, loading: false, notFound: false });
+ }
}
},
() => {
import * as React from 'react';
import { Link } from 'react-router';
import * as classNames from 'classnames';
+import * as PropTypes from 'prop-types';
import OpenCloseIcon from '../../../components/icons-components/OpenCloseIcon';
import {
activeOrChildrenActive,
} from '../utils';
import * as Docs from '../documentation.directory-loader';
+const pages = (Docs as any) as DocumentationEntry[];
+
interface Props {
splat?: string;
}
export default class Menu extends React.PureComponent<Props> {
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool
+ };
+
getMenuEntriesHierarchy = (root?: string): Array<DocumentationEntry> => {
- const toplevelEntries = getEntryChildren(Docs as any, root);
+ const instancePages = this.context.onSonarCloud
+ ? pages
+ : pages.filter(page => page.scope !== 'sonarcloud');
+ const toplevelEntries = getEntryChildren(instancePages, root);
toplevelEntries.forEach(entry => {
const entryRoot = getEntryRoot(entry.relativeName);
entry.children = entryRoot !== '' ? this.getMenuEntriesHierarchy(entryRoot) : [];
const opened = activeOrChildrenActive(this.props.splat || '', entry);
const offset = 10 + 25 * depth;
return (
- <React.Fragment key={entry.name}>
+ <React.Fragment key={entry.relativeName}>
<Link
className={classNames('list-group-item', { active })}
style={{ paddingLeft: offset }}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export interface DocumentationEntry {
- title: string;
- order: string;
+ children: DocumentationEntry[];
name: string;
+ order: string;
relativeName: string;
- children: Array<DocumentationEntry>;
+ scope?: 'sonarcloud';
+ title: string;
}
export function activeOrChildrenActive(root: string, entry: DocumentationEntry) {
return name;
}
-export function getEntryChildren(
- entries: Array<DocumentationEntry>,
- root?: string
-): Array<DocumentationEntry> {
+export function getEntryChildren(entries: DocumentationEntry[], root?: string) {
return entries.filter(entry => {
const parts = entry.relativeName.split('/');
const depth = root ? root.split('/').length : 0;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2018 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-//@flow
-import React from 'react';
-
-/*::
-type Props = {
- loading: boolean,
- total?: number,
- children?: React.Element<*>
-};
-*/
-
-export default class MembersPageHeader extends React.PureComponent {
- /*:: props: Props; */
-
- render() {
- return (
- <header className="page-header">
- {this.props.loading && <i className="spinner" />}
- {this.props.children}
- </header>
- );
- }
-}
--- /dev/null
+/*
+ * 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 { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router';
+import { translate } from '../../../helpers/l10n';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+
+interface Props {
+ children?: React.ReactNode;
+ loading: boolean;
+}
+
+export default function MembersPageHeader(props: Props) {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">{translate('organization.members.page')}</h1>
+ <DeferredSpinner loading={props.loading} />
+ {props.children}
+ <p className="page-description">
+ <FormattedMessage
+ defaultMessage={translate('organization.members.page.description')}
+ id="organization.members.page.description"
+ values={{
+ link: (
+ <Link to="/documentation/organizations/manage-team">
+ {translate('organization.members.manage_a_team')}
+ </Link>
+ )
+ }}
+ />
+ </p>
+ </header>
+ );
+}
import MembersListHeader from './MembersListHeader';
import MembersList from './MembersList';
import AddMemberForm from './forms/AddMemberForm';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
import ListFooter from '../../../components/controls/ListFooter';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate } from '../../../helpers/l10n';
/*:: import type { Organization, OrgGroup } from '../../../store/organizations/duck'; */
/*:: import type { Member } from '../../../store/organizationsMembers/actions'; */
return (
<div className="page page-limited">
<Helmet title={translate('organization.members.page')} />
- <MembersPageHeader loading={status.loading} total={status.total}>
+ <Suggestions suggestions="organization_members" />
+ <MembersPageHeader loading={status.loading}>
{organization.canAdmin && (
<div className="page-actions">
<AddMemberForm
addMember={this.addMember}
- organization={organization}
memberLogins={this.props.memberLogins}
+ organization={organization}
/>
+ <DocTooltip className="spacer-left" doc="organizations/add-organization-member" />
</div>
)}
</MembersPageHeader>
- <MembersListHeader total={status.total} handleSearch={this.handleSearchMembers} />
+ <MembersListHeader handleSearch={this.handleSearchMembers} total={status.total} />
<MembersList
members={members}
- organizationGroups={this.props.organizationGroups}
organization={organization}
+ organizationGroups={this.props.organizationGroups}
removeMember={this.removeMember}
updateMemberGroups={this.updateMemberGroups}
/>
{status.total != null && (
<ListFooter
count={members.length}
- total={status.total}
- ready={!status.loading}
loadMore={this.handleLoadMoreMembers}
+ ready={!status.loading}
+ total={status.total}
/>
)}
</div>
*/
import * as React from 'react';
import AllProjectsContainer from '../../projects/components/AllProjectsContainer';
+import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
interface Props {
location: { pathname: string; query: { [x: string]: string } };
export default function OrganizationProjects(props: Props) {
return (
- <AllProjectsContainer
- isFavorite={false}
- location={props.location}
- organization={props.organization}
- />
+ <>
+ <AllProjectsContainer
+ isFavorite={false}
+ location={props.location}
+ organization={props.organization}
+ />
+ <Suggestions suggestions="organization_projects" />
+ </>
);
}
+++ /dev/null
-/*
- * 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 React from 'react';
-import { shallow } from 'enzyme';
-import MembersPageHeader from '../MembersPageHeader';
-
-it('should render the members page header', () => {
- const wrapper = shallow(<MembersPageHeader />);
- expect(wrapper).toMatchSnapshot();
- wrapper.setProps({ loading: true });
- expect(wrapper.find('.spinner')).toMatchSnapshot();
-});
-
-it('should render the members page header with the total', () => {
- const wrapper = shallow(<MembersPageHeader total="5" />);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render its children', () => {
- const wrapper = shallow(
- <MembersPageHeader loading={true} total="5">
- <span>children test</span>
- </MembersPageHeader>
- );
- expect(wrapper).toMatchSnapshot();
-});
--- /dev/null
+/*
+ * 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 MembersPageHeader from '../MembersPageHeader';
+
+it('should render', () => {
+ const wrapper = shallow(
+ <MembersPageHeader loading={true}>
+ <span>children test</span>
+ </MembersPageHeader>
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render its children 1`] = `
-<header
- className="page-header"
->
- <i
- className="spinner"
- />
- <span>
- children test
- </span>
-</header>
-`;
-
-exports[`should render the members page header 1`] = `
-<header
- className="page-header"
-/>
-`;
-
-exports[`should render the members page header 2`] = `
-<i
- className="spinner"
-/>
-`;
-
-exports[`should render the members page header with the total 1`] = `
-<header
- className="page-header"
-/>
-`;
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should render 1`] = `
+<header
+ className="page-header"
+>
+ <h1
+ className="page-title"
+ >
+ organization.members.page
+ </h1>
+ <DeferredSpinner
+ loading={true}
+ timeout={100}
+ />
+ <span>
+ children test
+ </span>
+ <p
+ className="page-description"
+ >
+ <FormattedMessage
+ defaultMessage="organization.members.page.description"
+ id="organization.members.page.description"
+ values={
+ Object {
+ "link": <Link
+ onlyActiveOnIndex={false}
+ style={Object {}}
+ to="/documentation/organizations/manage-team"
+ >
+ organization.members.manage_a_team
+ </Link>,
+ }
+ }
+ />
+ </p>
+</header>
+`;
encodeSpecialCharacters={true}
title="organization.members.page"
/>
- <MembersPageHeader
- total={2}
+ <Suggestions
+ suggestions="organization_members"
/>
+ <MembersPageHeader />
<MembersListHeader
handleSearch={[Function]}
total={2}
encodeSpecialCharacters={true}
title="organization.members.page"
/>
+ <Suggestions
+ suggestions="organization_members"
+ />
<MembersPageHeader
loading={true}
- total={2}
>
<div
className="page-actions"
}
}
/>
+ <DocTooltip
+ className="spacer-left"
+ doc="organizations/add-organization-member"
+ />
</div>
</MembersPageHeader>
<MembersListHeader
import remark from 'remark';
import reactRenderer from 'remark-react';
import * as matter from 'gray-matter';
+import * as PropTypes from 'prop-types';
import DocLink from './DocLink';
import DocParagraph from './DocParagraph';
import DocImg from './DocImg';
displayH1?: boolean;
}
-export default function DocMarkdownBlock({ className, content, displayH1 }: Props) {
- const parsed = matter(content || '');
- return (
- <div className={classNames('markdown', className)}>
- {displayH1 && <h1>{parsed.data.title}</h1>}
- {
- remark()
- // .use(remarkInclude)
- .use(reactRenderer, {
- remarkReactComponents: {
- // do not render outer <div />
- div: React.Fragment,
- // use custom link to render documentation anchors
- a: DocLink,
- // used to handle `@include`
- p: DocParagraph,
- // use custom img tag to render documentation images
- img: DocImg
- },
- toHast: {}
- })
- .processSync(parsed.content).contents
- }
- </div>
- );
+export default class DocMarkdownBlock extends React.PureComponent<Props> {
+ static contextTypes = {
+ onSonarCloud: PropTypes.bool
+ };
+
+ render() {
+ const { className, content, displayH1 } = this.props;
+ const parsed = matter(content || '');
+ return (
+ <div className={classNames('markdown', className)}>
+ {displayH1 && <h1>{parsed.data.title}</h1>}
+ {
+ remark()
+ // .use(remarkInclude)
+ .use(reactRenderer, {
+ remarkReactComponents: {
+ // do not render outer <div />
+ div: React.Fragment,
+ // use custom link to render documentation anchors
+ a: DocLink,
+ // used to handle `@include`
+ p: DocParagraph,
+ // use custom img tag to render documentation images
+ img: DocImg
+ },
+ toHast: {}
+ })
+ .processSync(filterContent(parsed.content, this.context.onSonarCloud)).contents
+ }
+ </div>
+ );
+ }
+}
+
+function filterContent(content: string, onSonarCloud: boolean) {
+ const beginning = onSonarCloud ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
+ const ending = onSonarCloud ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
+
+ let newContent = content;
+ let start = newContent.indexOf(beginning);
+ let end = newContent.indexOf(ending);
+ while (start !== -1 && end !== -1) {
+ newContent = newContent.substring(0, start) + newContent.substring(end + ending.length);
+ start = newContent.indexOf(beginning);
+ end = newContent.indexOf(ending);
+ }
+
+ return newContent;
}
shallow(<DocMarkdownBlock content="some [link](#quality-profiles)" />).find('DocLink')
).toMatchSnapshot();
});
+
+it.only('should cut sonarqube/sonarcloud content', () => {
+ const content = `
+some
+
+<!-- sonarqube -->
+sonarqube
+<!-- /sonarqube -->
+
+<!-- sonarcloud -->
+sonarcloud
+<!-- /sonarcloud -->
+
+<!-- sonarqube -->
+ long
+
+ multiline
+<!-- /sonarqube -->
+
+text`;
+
+ expect(
+ shallow(<DocMarkdownBlock content={content} />, { context: { onSonarCloud: false } })
+ ).toMatchSnapshot();
+
+ expect(
+ shallow(<DocMarkdownBlock content={content} />, { context: { onSonarCloud: true } })
+ ).toMatchSnapshot();
+});
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should cut sonarqube/sonarcloud content 1`] = `
+<div
+ className="markdown"
+>
+ <React.Fragment
+ key="h-1"
+ >
+ <DocParagraph
+ key="h-2"
+ >
+ some
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-3"
+ >
+ sonarqube
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-4"
+ >
+ long
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-5"
+ >
+ multiline
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-6"
+ >
+ text
+ </DocParagraph>
+ </React.Fragment>
+</div>
+`;
+
+exports[`should cut sonarqube/sonarcloud content 2`] = `
+<div
+ className="markdown"
+>
+ <React.Fragment
+ key="h-1"
+ >
+ <DocParagraph
+ key="h-2"
+ >
+ some
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-3"
+ >
+ sonarcloud
+ </DocParagraph>
+
+
+ <DocParagraph
+ key="h-4"
+ >
+ text
+ </DocParagraph>
+ </React.Fragment>
+</div>
+`;
+
exports[`should render simple markdown 1`] = `
<div
className="markdown"
organization.url=Url
organization.url.description=Url of the homepage of the organization.
organization.members.page=Members
+organization.members.page.description=Add users to the organization and grant them permissions to work on the projects. See {link} documentation.
organization.members.add=Add a member
organization.members.x_groups={0} group(s)
organization.members.members=member(s)
organization.members.remove_x=Are you sure you want to remove {0} from {1}'s members ?
organization.members.manage_groups=Manage groups
organization.members.members_groups={0}'s groups:
+organization.members.manage_a_team=Manage a team
organization.members.add_to_members=Add to members
organization.default_visibility_of_new_projects=Default visibility of new projects:
organization.change_visibility_form.header=Set Default Visibility of New Projects