--- /dev/null
+This organization is subscribed to a paid plan, allowing private projects. Its private projects, members, Quality Profiles and Quality Gates are visible to members only.
+
+---
+
+See also: [Organization and Project Privacy](/organizations/organization-and-project-privacy)
--- /dev/null
+This project is private. Only the members of this organization are able to browse it and its source code.
+
+---
+
+See also: [Organization and Project Privacy](/organizations/organization-and-project-privacy)
--- /dev/null
+This project is public, which means anyone is able to browse its source code. Subscribe to a paid plan to get unlimited private projects in [Administration > Billing](/#sonarcloud#/organizations/#organization#/extension/billing/billing).
+
+---
+
+See also: [Pricing](/sonarcloud-pricing)
--- /dev/null
+This project is public, which means anyone is able to browse its source code. Go to your project's [Administration > Permissions](/#sonarcloud#/project_roles?id=#projectKey#) to make it private.
+
+---
+
+See also: [Organization and Project Privacy](/organizations/organization-and-project-privacy)
--- /dev/null
+This project is public, which means anyone is able to browse its source code. Contact the project administrator to make it private.
+
+---
+
+See also: [Organization and Project Privacy](/organizations/organization-and-project-privacy)
--- /dev/null
+This project is public, which means anyone is able to browse its source code. Contact the organization administrator if you want to make it private.
+
+---
+
+See also: [Pricing](/sonarcloud-pricing)
isFavorite?: boolean;
analysisDate?: string;
tags: string[];
- visibility: string;
+ visibility: Visibility;
leakPeriodDate?: string;
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { BaseSearchProjectsParameters } from './components';
-import { PermissionTemplate } from '../app/types';
+import { PermissionTemplate, Visibility } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
export function changeProjectVisibility(
project: string,
- visibility: string
+ visibility: Visibility
): Promise<void | Response> {
return post('/api/projects/update_visibility', { project, visibility }).catch(throwGlobalError);
}
export function changeProjectDefaultVisibility(
organization: string,
- projectVisibility: string
+ projectVisibility: Visibility
): Promise<void | Response> {
return post('/api/projects/update_default_visibility', { organization, projectVisibility }).catch(
throwGlobalError
MainBranch,
LongLivingBranch,
PullRequest,
- BranchType
+ BranchType,
+ Visibility
} from '../../types';
import { STATUSES } from '../../../apps/background-tasks/constants';
import { waitAndUpdate } from '../../../helpers/testUtils';
(wrapper.instance() as ComponentContainer).mounted = true;
wrapper.setState({
branches: [{ isMain: true }],
- component: { qualifier: 'TRK', visibility: 'public' },
+ component: { qualifier: 'TRK', visibility: Visibility.Public },
loading: false
});
- (wrapper.find(Inner).prop('onComponentChange') as Function)({ visibility: 'private' });
- expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: 'private' });
+ (wrapper.find(Inner).prop('onComponentChange') as Function)({ visibility: Visibility.Private });
+ expect(wrapper.state().component).toEqual({ qualifier: 'TRK', visibility: Visibility.Private });
});
it("loads branches for module's project", async () => {
import OrganizationAvatar from '../../../../components/common/OrganizationAvatar';
import OrganizationHelmet from '../../../../components/common/OrganizationHelmet';
import OrganizationLink from '../../../../components/ui/OrganizationLink';
-import PrivateBadge from '../../../../components/common/PrivateBadge';
import { collapsePath, limitComponentName } from '../../../../helpers/path';
import { getProjectUrl } from '../../../../helpers/urls';
</>
)}
{renderBreadcrumbs(component.breadcrumbs)}
- {component.visibility === 'private' && (
- <PrivateBadge className="spacer-left" qualifier={component.qualifier} />
- )}
{props.currentBranchLike && (
<ComponentNavBranch
branchLikes={props.branchLikes}
name: 'My Project',
organization: 'org',
qualifier: 'TRK',
- visibility: 'public'
+ visibility: Visibility.Public
};
const result = shallow(
<ComponentNavHeader
name: 'My Project',
organization: 'foo',
qualifier: 'TRK',
- visibility: 'public'
+ visibility: Visibility.Public
};
const organization = {
key: 'foo',
);
expect(result).toMatchSnapshot();
});
-
-it('renders private badge', () => {
- const component = {
- breadcrumbs: [{ key: 'my-project', name: 'My Project', qualifier: 'TRK' }],
- key: 'my-project',
- name: 'My Project',
- organization: 'org',
- qualifier: 'TRK',
- visibility: 'private'
- };
- const result = shallow(
- <ComponentNavHeader
- branchLikes={[]}
- component={component}
- currentBranchLike={undefined}
- shouldOrganizationBeDisplayed={false}
- />
- );
- expect(result.find('PrivateBadge')).toHaveLength(1);
-});
.outline-badge.active {
color: var(--baseFontColor);
- border: 1px solid var(--blue);
+ border-color: var(--blue);
background-color: var(--lightBlue);
}
+
+.outline-badge.badge-info {
+ border-color: var(--blue);
+}
+
+.outline-badge.badge-icon {
+ padding-left: calc(var(--gridSize) / 2);
+}
+
+.outline-badge.badge-icon svg {
+ height: calc(var(--smallControlHeight) - 2px);
+}
qualityGate?: { isDefault?: boolean; key: string; name: string };
tags?: string[];
version?: string;
- visibility?: string;
+ visibility?: Visibility;
}
interface ComponentConfiguration {
import Helmet from 'react-helmet';
import { connect } from 'react-redux';
import OrganizationNavigation from '../navigation/OrganizationNavigation';
+import { fetchOrganization } from '../actions';
import NotFound from '../../../app/components/NotFound';
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions';
-import { fetchOrganization } from '../actions';
-import { getOrganizationByKey } from '../../../store/rootReducer';
-import { Organization } from '../../../app/types';
+import { Organization, CurrentUser } from '../../../app/types';
+import {
+ getOrganizationByKey,
+ getCurrentUser,
+ getMyOrganizations
+} from '../../../store/rootReducer';
interface OwnProps {
children?: React.ReactNode;
}
interface StateProps {
+ currentUser: CurrentUser;
organization?: Organization;
+ userOrganizations: Organization[];
}
interface DispatchToProps {
<div>
<Helmet defaultTitle={organization.name} titleTemplate={'%s - ' + organization.name} />
<Suggestions suggestions="organization_space" />
- <OrganizationNavigation location={this.props.location} organization={organization} />
+ <OrganizationNavigation
+ currentUser={this.props.currentUser}
+ location={this.props.location}
+ organization={organization}
+ userOrganizations={this.props.userOrganizations}
+ />
{this.props.children}
</div>
);
}
const mapStateToProps = (state: any, ownProps: OwnProps) => ({
- organization: getOrganizationByKey(state, ownProps.params.organizationKey)
+ currentUser: getCurrentUser(state),
+ organization: getOrganizationByKey(state, ownProps.params.organizationKey),
+ userOrganizations: getMyOrganizations(state)
});
const mapDispatchToProps = { fetchOrganization: fetchOrganization as any };
import { shallow } from 'enzyme';
import { OrganizationPage } from '../OrganizationPage';
-const fetchOrganization = () => Promise.resolve();
+const fetchOrganization = jest.fn().mockResolvedValue(undefined);
+
+beforeEach(() => {
+ fetchOrganization.mockClear();
+});
it('smoke test', () => {
- const wrapper = shallow(
- <OrganizationPage
- fetchOrganization={fetchOrganization}
- location={{ pathname: 'foo' }}
- params={{ organizationKey: 'foo' }}>
- <div>hello</div>
- </OrganizationPage>
- );
+ const wrapper = getWrapper();
expect(wrapper.type()).toBeNull();
const organization = { key: 'foo', name: 'Foo', isDefault: false, canAdmin: false };
});
it('not found', () => {
- const wrapper = shallow(
- <OrganizationPage
- fetchOrganization={fetchOrganization}
- location={{ pathname: 'foo' }}
- params={{ organizationKey: 'foo' }}>
- <div>hello</div>
- </OrganizationPage>
- );
+ const wrapper = getWrapper();
wrapper.setState({ loading: false });
expect(wrapper).toMatchSnapshot();
});
it('should correctly update when the organization changes', () => {
- const fetchOrganization = jest.fn(() => Promise.resolve());
- const wrapper = shallow(
+ const wrapper = getWrapper();
+ wrapper.setProps({ params: { organizationKey: 'bar' } });
+ expect(fetchOrganization).toHaveBeenCalledTimes(2);
+ expect(fetchOrganization.mock.calls).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
<OrganizationPage
+ currentUser={{ isLoggedIn: false }}
fetchOrganization={fetchOrganization}
location={{ pathname: 'foo' }}
- params={{ organizationKey: 'foo' }}>
+ params={{ organizationKey: 'foo' }}
+ userOrganizations={[]}
+ {...props}>
<div>hello</div>
</OrganizationPage>
);
- wrapper.setProps({ params: { organizationKey: 'bar' } });
- expect(fetchOrganization).toHaveBeenCalledTimes(2);
- expect(fetchOrganization.mock.calls).toMatchSnapshot();
-});
+}
suggestions="organization_space"
/>
<OrganizationNavigation
+ currentUser={
+ Object {
+ "isLoggedIn": false,
+ }
+ }
location={
Object {
"pathname": "foo",
"name": "Foo",
}
}
+ userOrganizations={Array []}
/>
<div>
hello
import OrganizationNavigationMenuContainer from './OrganizationNavigationMenuContainer';
import * as theme from '../../../app/theme';
import ContextNavBar from '../../../components/nav/ContextNavBar';
-import { Organization } from '../../../app/types';
+import { Organization, CurrentUser } from '../../../app/types';
interface Props {
+ currentUser: CurrentUser;
location: { pathname: string };
organization: Organization;
+ userOrganizations: Organization[];
}
-export default function OrganizationNavigation({ location, organization }: Props) {
+export default function OrganizationNavigation({
+ currentUser,
+ location,
+ organization,
+ userOrganizations
+}: Props) {
return (
<ContextNavBar height={theme.contextNavHeightRaw} id="context-navigation">
<div className="navbar-context-justified">
<OrganizationNavigationHeaderContainer organization={organization} />
- <OrganizationNavigationMeta organization={organization} />
+ <OrganizationNavigationMeta
+ currentUser={currentUser}
+ organization={organization}
+ userOrganizations={userOrganizations}
+ />
</div>
<OrganizationNavigationMenuContainer location={location} organization={organization} />
</ContextNavBar>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { Organization, HomePageType } from '../../../app/types';
import HomePageSelect from '../../../components/controls/HomePageSelect';
+import DocTooltip from '../../../components/docs/DocTooltip';
import { translate } from '../../../helpers/l10n';
import { isSonarCloud } from '../../../helpers/system';
+import { hasPrivateAccess, isPaidOrganization } from '../../../helpers/organizations';
+import { CurrentUser, HomePageType, Organization } from '../../../app/types';
interface Props {
+ currentUser: CurrentUser;
organization: Organization;
+ userOrganizations: Organization[];
}
-export default function OrganizationNavigationMeta({ organization }: Props) {
+export default function OrganizationNavigationMeta({
+ currentUser,
+ organization,
+ userOrganizations
+}: Props) {
+ const onSonarCloud = isSonarCloud();
return (
<div className="navbar-context-meta">
{organization.url != null && (
{organization.url}
</a>
)}
+ {onSonarCloud &&
+ isPaidOrganization(organization) &&
+ hasPrivateAccess(currentUser, organization, userOrganizations) && (
+ <DocTooltip className="spacer-right" doc="organizations/subscription-paid-plan">
+ <div className="outline-badge">{translate('organization.paid_plan.badge')}</div>
+ </DocTooltip>
+ )}
<div className="text-muted">
<strong>{translate('organization.key')}:</strong> {organization.key}
</div>
- {isSonarCloud() && (
+ {onSonarCloud && (
<div className="navbar-context-meta-secondary">
<HomePageSelect
currentPage={{ type: HomePageType.Organization, organization: organization.key }}
expect(
shallow(
<OrganizationNavigation
+ currentUser={{ isLoggedIn: false }}
location={{ pathname: '/organizations/foo' }}
organization={{
key: 'foo',
name: 'Foo',
projectVisibility: Visibility.Public
}}
+ userOrganizations={[]}
/>
)
).toMatchSnapshot();
import * as React from 'react';
import { shallow } from 'enzyme';
import OrganizationNavigationMeta from '../OrganizationNavigationMeta';
-import { Visibility } from '../../../../app/types';
+import { OrganizationSubscription } from '../../../../app/types';
jest.mock('../../../../helpers/system', () => ({ isSonarCloud: () => true }));
+const organization = { key: 'foo', name: 'Foo', subscription: OrganizationSubscription.Free };
+
it('renders', () => {
expect(
shallow(
<OrganizationNavigationMeta
- organization={{
- key: 'foo',
- name: 'Foo',
- projectVisibility: Visibility.Public
- }}
+ currentUser={{ isLoggedIn: false }}
+ organization={organization}
+ userOrganizations={[]}
/>
)
).toMatchSnapshot();
});
+
+it('renders with private badge', () => {
+ expect(
+ shallow(
+ <OrganizationNavigationMeta
+ currentUser={{ isLoggedIn: true }}
+ organization={{ ...organization, subscription: OrganizationSubscription.Paid }}
+ userOrganizations={[organization]}
+ />
+ )
+ .find('DocTooltip')
+ .exists()
+ ).toBeTruthy();
+});
}
/>
<OrganizationNavigationMeta
+ currentUser={
+ Object {
+ "isLoggedIn": false,
+ }
+ }
organization={
Object {
"key": "foo",
"projectVisibility": "public",
}
}
+ userOrganizations={Array []}
/>
</div>
<Connect(OrganizationNavigationMenu)
getMyOrganizations,
getOrganizationByKey
} from '../../../store/rootReducer';
+import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
interface StateToProps {
currentUser: CurrentUser;
render() {
const { organizationsEnabled } = this.context;
- const { branchLike, component, metrics } = this.props;
+ const { branchLike, component, metrics, organization } = this.props;
const { qualifier, description, visibility } = component;
const isProject = qualifier === 'TRK';
const isApp = qualifier === 'APP';
const isPrivate = visibility === Visibility.Private;
-
return (
<div className="overview-meta">
<div className="overview-meta-card">
<h4 className="overview-meta-header">
{translate('overview.about_this_project', qualifier)}
+ {component.visibility && (
+ <PrivacyBadgeContainer
+ className="spacer-left pull-right"
+ organization={organization}
+ qualifier={component.qualifier}
+ tooltipProps={{ projectKey: component.key }}
+ visibility={component.visibility}
+ />
+ )}
</h4>
{description !== undefined && <p className="overview-meta-description">{description}</p>}
{isProject && (
import HoldersList from '../../shared/components/HoldersList';
import { translate } from '../../../../helpers/l10n';
import { PERMISSIONS_ORDER_BY_QUALIFIER } from '../constants';
+import { Visibility } from '../../../../app/types';
/*::
type Props = {|
render() {
let order = PERMISSIONS_ORDER_BY_QUALIFIER[this.props.component.qualifier];
- if (this.props.visibility === 'public') {
+ if (this.props.visibility === Visibility.Public) {
order = without(order, 'user', 'codeviewer');
}
import * as api from '../../../../api/permissions';
import { translate } from '../../../../helpers/l10n';
import '../../styles.css';
+import { Visibility } from '../../../../app/types';
/*::
export type Props = {|
};
handleVisibilityChange = (visibility /*: string */) => {
- if (visibility === 'public') {
+ if (visibility === Visibility.Public) {
this.openDisclaimer();
} else {
this.turnProjectToPrivate();
};
turnProjectToPublic = () => {
- this.props.onComponentChange({ visibility: 'public' });
- api.changeProjectVisibility(this.props.component.key, 'public').then(
+ this.props.onComponentChange({ visibility: Visibility.Public });
+ api.changeProjectVisibility(this.props.component.key, Visibility.Public).then(
() => {
this.loadHolders();
},
error => {
- this.props.onComponentChange({ visibility: 'private' });
+ this.props.onComponentChange({ visibility: Visibility.Private });
}
);
};
turnProjectToPrivate = () => {
- this.props.onComponentChange({ visibility: 'private' });
- api.changeProjectVisibility(this.props.component.key, 'private').then(
+ this.props.onComponentChange({ visibility: Visibility.Private });
+ api.changeProjectVisibility(this.props.component.key, Visibility.Private).then(
() => {
this.loadHolders();
},
error => {
- this.props.onComponentChange({ visibility: 'public' });
+ this.props.onComponentChange({ visibility: Visibility.Public });
}
);
};
import { getMetrics } from '../../../store/rootReducer';
import { Metric, Component } from '../../../app/types';
import '../styles.css';
+import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
interface OwnProps {
component: Component;
<div className="portfolio-meta-card">
<h4 className="portfolio-meta-header">
{translate('overview.about_this_portfolio')}
+ {component.visibility && (
+ <PrivacyBadgeContainer
+ className="spacer-left pull-right"
+ organization={component.organization}
+ qualifier={component.qualifier}
+ tooltipProps={{ projectKey: component.key }}
+ visibility={component.visibility}
+ />
+ )}
</h4>
<Summary component={component} measures={measures || {}} />
</div>
import DateFromNow from '../../../components/intl/DateFromNow';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import TagsList from '../../../components/tags/TagsList';
-import PrivateBadge from '../../../components/common/PrivateBadge';
+import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Project } from '../types';
+import { Organization } from '../../../app/types';
interface Props {
height: number;
- organization?: { key: string };
+ organization: Organization | undefined;
project: Project;
}
export default function ProjectCardLeak({ height, organization, project }: Props) {
const { measures } = project;
-
- const isPrivate = project.visibility === 'private';
const hasTags = project.tags.length > 0;
return (
)}
<Link to={{ pathname: '/dashboard', query: { id: project.key } }}>{project.name}</Link>
</h2>
- {project.analysisDate && <ProjectCardQualityGate status={measures!['alert_status']} />}
+ {project.analysisDate && <ProjectCardQualityGate status={measures['alert_status']} />}
<div className="project-card-header-right">
- {isPrivate && <PrivateBadge className="spacer-left" qualifier="TRK" />}
+ <PrivacyBadgeContainer
+ className="spacer-left"
+ organization={organization || project.organization}
+ qualifier="TRK"
+ tooltipProps={{ projectKey: project.key }}
+ visibility={project.visibility}
+ />
+
{hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
</div>
{project.analysisDate &&
project.leakPeriodDate && (
<div className="project-card-dates note text-right pull-right">
- <DateFromNow date={project.leakPeriodDate!}>
+ <DateFromNow date={project.leakPeriodDate}>
{fromNow => (
<span className="project-card-leak-date pull-right">
{translateWithParameters('projects.leak_period_x', fromNow)}
</span>
)}
</DateFromNow>
- <DateTimeFormatter date={project.analysisDate!}>
+ <DateTimeFormatter date={project.analysisDate}>
{formattedDate => (
<span>
{translateWithParameters('projects.last_analysis_on_x', formattedDate)}
import Favorite from '../../../components/controls/Favorite';
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter';
import TagsList from '../../../components/tags/TagsList';
-import PrivateBadge from '../../../components/common/PrivateBadge';
+import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Project } from '../types';
+import { Organization } from '../../../app/types';
interface Props {
height: number;
- organization?: { key: string };
+ organization: Organization | undefined;
project: Project;
}
export default function ProjectCardOverall({ height, organization, project }: Props) {
const { measures } = project;
- const isPrivate = project.visibility === 'private';
const hasTags = project.tags.length > 0;
return (
</h2>
{project.analysisDate && <ProjectCardQualityGate status={measures['alert_status']} />}
<div className="project-card-header-right">
- {isPrivate && <PrivateBadge className="spacer-left" qualifier="TRK" />}
+ <PrivacyBadgeContainer
+ className="spacer-left"
+ organization={organization || project.organization}
+ qualifier="TRK"
+ tooltipProps={{ projectKey: project.key }}
+ visibility={project.visibility}
+ />
{hasTags && <TagsList className="spacer-left note" tags={project.tags} />}
</div>
</div>
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardLeak from '../ProjectCardLeak';
+import { Visibility } from '../../../../app/types';
const MEASURES = {
alert_status: 'OK',
name: 'Foo',
organization: { key: 'org', name: 'org' },
tags: [],
- visibility: 'public'
+ visibility: Visibility.Public
};
it('should display analysis date and leak start date', () => {
- const card = shallow(<ProjectCardLeak height={100} project={PROJECT} />);
+ const card = shallow(<ProjectCardLeak height={100} organization={undefined} project={PROJECT} />);
expect(card.find('.project-card-dates').exists()).toBeTruthy();
expect(card.find('.project-card-dates').find('DateFromNow')).toHaveLength(1);
expect(card.find('.project-card-dates').find('DateTimeFormatter')).toHaveLength(1);
it('should not display analysis date or leak start date', () => {
const project = { ...PROJECT, analysisDate: undefined };
- const card = shallow(<ProjectCardLeak height={100} project={project} />);
+ const card = shallow(<ProjectCardLeak height={100} organization={undefined} project={project} />);
expect(card.find('.project-card-dates').exists()).toBeFalsy();
});
it('should display tags', () => {
const project = { ...PROJECT, tags: ['foo', 'bar'] };
expect(
- shallow(<ProjectCardLeak height={100} project={project} />)
+ shallow(<ProjectCardLeak height={100} organization={undefined} project={project} />)
.find('TagsList')
.exists()
).toBeTruthy();
});
-it('should private badge', () => {
- const project = { ...PROJECT, visibility: 'private' };
+it('should display private badge', () => {
+ const project = { ...PROJECT, visibility: Visibility.Private };
expect(
- shallow(<ProjectCardLeak height={100} project={project} />)
- .find('PrivateBadge')
+ shallow(<ProjectCardLeak height={100} organization={undefined} project={project} />)
+ .find('Connect(PrivacyBadge)')
.exists()
).toBeTruthy();
});
it('should display the leak measures and quality gate', () => {
- expect(shallow(<ProjectCardLeak height={100} project={PROJECT} />)).toMatchSnapshot();
+ expect(
+ shallow(<ProjectCardLeak height={100} organization={undefined} project={PROJECT} />)
+ ).toMatchSnapshot();
});
import * as React from 'react';
import { shallow } from 'enzyme';
import ProjectCardOverall from '../ProjectCardOverall';
+import { Visibility } from '../../../../app/types';
const MEASURES = {
alert_status: 'OK',
name: 'Foo',
organization: { key: 'org', name: 'org' },
tags: [],
- visibility: 'public'
+ visibility: Visibility.Public
};
it('should display analysis date (and not leak period) when defined', () => {
expect(
- shallow(<ProjectCardOverall height={100} project={PROJECT} />)
+ shallow(<ProjectCardOverall height={100} organization={undefined} project={PROJECT} />)
.find('.project-card-dates')
.exists()
).toBeTruthy();
expect(
- shallow(<ProjectCardOverall height={100} project={{ ...PROJECT, analysisDate: undefined }} />)
+ shallow(
+ <ProjectCardOverall
+ height={100}
+ organization={undefined}
+ project={{ ...PROJECT, analysisDate: undefined }}
+ />
+ )
.find('.project-card-dates')
.exists()
).toBeFalsy();
it('should not display the quality gate', () => {
const project = { ...PROJECT, analysisDate: undefined };
expect(
- shallow(<ProjectCardOverall height={100} project={project} />)
+ shallow(<ProjectCardOverall height={100} organization={undefined} project={project} />)
.find('ProjectCardOverallQualityGate')
.exists()
).toBeFalsy();
it('should display tags', () => {
const project = { ...PROJECT, tags: ['foo', 'bar'] };
expect(
- shallow(<ProjectCardOverall height={100} project={project} />)
+ shallow(<ProjectCardOverall height={100} organization={undefined} project={project} />)
.find('TagsList')
.exists()
).toBeTruthy();
});
-it('should private badge', () => {
- const project = { ...PROJECT, visibility: 'private' };
+it('should display private badge', () => {
+ const project = { ...PROJECT, visibility: Visibility.Private };
expect(
- shallow(<ProjectCardOverall height={100} project={project} />)
- .find('PrivateBadge')
+ shallow(<ProjectCardOverall height={100} organization={undefined} project={project} />)
+ .find('Connect(PrivacyBadge)')
.exists()
).toBeTruthy();
});
it('should display the overall measures and quality gate', () => {
- expect(shallow(<ProjectCardOverall height={100} project={PROJECT} />)).toMatchSnapshot();
+ expect(
+ shallow(<ProjectCardOverall height={100} organization={undefined} project={PROJECT} />)
+ ).toMatchSnapshot();
});
/>
<div
className="project-card-header-right"
- />
+ >
+ <Connect(PrivacyBadge)
+ className="spacer-left"
+ organization={
+ Object {
+ "key": "org",
+ "name": "org",
+ }
+ }
+ qualifier="TRK"
+ tooltipProps={
+ Object {
+ "projectKey": "foo",
+ }
+ }
+ visibility="public"
+ />
+ </div>
</div>
<div
className="project-card-dates note text-right pull-right"
/>
<div
className="project-card-header-right"
- />
+ >
+ <Connect(PrivacyBadge)
+ className="spacer-left"
+ organization={
+ Object {
+ "key": "org",
+ "name": "org",
+ }
+ }
+ qualifier="TRK"
+ tooltipProps={
+ Object {
+ "projectKey": "foo",
+ }
+ }
+ visibility="public"
+ />
+ </div>
</div>
<div
className="project-card-dates note text-right"
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Visibility } from '../../app/types';
+
export interface Project {
analysisDate?: string;
isFavorite?: boolean;
name: string;
organization?: { key: string; name: string };
tags: string[];
- visibility: string;
+ visibility: Visibility;
}
export interface Facet {
import * as React from 'react';
import { shallow } from 'enzyme';
import Risk from '../Risk';
+import { Visibility } from '../../../../app/types';
it('renders', () => {
const project1 = {
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734' },
name: 'Foo',
tags: [],
- visibility: 'public'
+ visibility: Visibility.Public
};
expect(
shallow(<Risk displayOrganizations={false} helpText="foobar" projects={[project1]} />)
import * as React from 'react';
import { shallow } from 'enzyme';
import SimpleBubbleChart from '../SimpleBubbleChart';
+import { Visibility } from '../../../../app/types';
it('renders', () => {
const project1 = {
measures: { complexity: '17.2', coverage: '53.5', ncloc: '1734', security_rating: '2' },
name: 'Foo',
tags: [],
- visibility: 'public'
+ visibility: Visibility.Public
};
expect(
shallow(
export interface Props {
currentUser: { login: string };
hasProvisionPermission?: boolean;
- onVisibilityChange: (visibility: string) => void;
+ onVisibilityChange: (visibility: Visibility) => void;
organization: Organization;
topLevelQualifiers: string[];
}
<Projects
currentUser={this.props.currentUser}
- ready={this.state.ready}
- projects={this.state.projects}
- selection={this.state.selection}
- onProjectSelected={this.onProjectSelected}
onProjectDeselected={this.onProjectDeselected}
+ onProjectSelected={this.onProjectSelected}
organization={this.props.organization}
+ projects={this.state.projects}
+ ready={this.state.ready}
+ selection={this.state.selection}
/>
<ListFooter
- ready={this.state.ready}
count={this.state.projects.length}
- total={this.state.total}
loadMore={this.loadMore}
+ ready={this.state.ready}
+ total={this.state.total}
/>
{this.state.createProjectForm && (
interface DispatchProps {
fetchOrganization: (organization: string) => void;
- onVisibilityChange: (organization: Organization, visibility: string) => void;
+ onVisibilityChange: (organization: Organization, visibility: Visibility) => void;
}
interface OwnProps {
}
}
- handleVisibilityChange = (visibility: string) => {
+ handleVisibilityChange = (visibility: Visibility) => {
if (this.props.organization) {
this.props.onVisibilityChange(this.props.organization, visibility);
}
import { Link } from 'react-router';
import ProjectRowActions from './ProjectRowActions';
import { Project } from './utils';
-import { Visibility } from '../../app/types';
-import PrivateBadge from '../../components/common/PrivateBadge';
+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';
};
render() {
- const { project, selected } = this.props;
+ const { organization, project, selected } = this.props;
return (
<tr>
</td>
<td className="thin nowrap">
- {project.visibility === Visibility.Private && (
- <PrivateBadge qualifier={project.qualifier} />
- )}
+ <PrivacyBadgeContainer
+ organization={organization}
+ qualifier={project.qualifier}
+ tooltipProps={{ projectKey: project.key }}
+ visibility={project.visibility}
+ />
</td>
<td className="nowrap">
<td className="thin nowrap">
<ProjectRowActions
currentUser={this.props.currentUser}
- organization={this.props.organization}
+ organization={organization}
project={project}
/>
</td>
it('changes default project visibility', () => {
const onVisibilityChange = jest.fn();
const wrapper = shallowRender({ onVisibilityChange });
- wrapper.find('Header').prop<Function>('onVisibilityChange')('private');
- expect(onVisibilityChange).toBeCalledWith('private');
+ wrapper.find('Header').prop<Function>('onVisibilityChange')(Visibility.Private);
+ expect(onVisibilityChange).toBeCalledWith(Visibility.Private);
});
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
click(wrapper.find('a[data-visibility="private"]'), {
currentTarget: {
blur() {},
- dataset: { visibility: 'private' }
+ dataset: { visibility: Visibility.Private }
}
});
expect(wrapper).toMatchSnapshot();
click(wrapper.find('.js-confirm'));
- expect(onConfirm).toBeCalledWith('private');
+ expect(onConfirm).toBeCalledWith(Visibility.Private);
});
function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
change(wrapper.find('input[name="key"]'), 'key', {
currentTarget: { name: 'key', value: 'key' }
});
- wrapper.find('VisibilitySelector').prop<Function>('onChange')('private');
+ wrapper.find('VisibilitySelector').prop<Function>('onChange')(Visibility.Private);
wrapper.update();
expect(wrapper).toMatchSnapshot();
name: 'name',
organization: 'org',
project: 'key',
- visibility: 'private'
+ visibility: Visibility.Private
});
expect(wrapper).toMatchSnapshot();
import Projects from '../Projects';
import { Visibility } from '../../../app/types';
-const organization = { key: 'org', name: 'org', projectVisibility: 'public' };
+const organization = { key: 'org', name: 'org', projectVisibility: Visibility.Public };
const projects = [
{ key: 'a', name: 'A', qualifier: 'TRK', visibility: Visibility.Public },
{ key: 'b', name: 'B', qualifier: 'TRK', visibility: Visibility.Public }
<td
className="thin nowrap"
>
- <PrivateBadge
+ <Connect(PrivacyBadge)
qualifier="TRK"
+ tooltipProps={
+ Object {
+ "projectKey": "project",
+ }
+ }
+ visibility="private"
/>
</td>
<td
<td
className="thin nowrap"
>
- <PrivateBadge
+ <Connect(PrivacyBadge)
qualifier="TRK"
+ tooltipProps={
+ Object {
+ "projectKey": "project",
+ }
+ }
+ visibility="private"
/>
</td>
<td
--- /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 * as classNames from 'classnames';
+import { connect } from 'react-redux';
+import * as theme from '../../app/theme';
+import Tooltip from '../controls/Tooltip';
+import { translate } from '../../helpers/l10n';
+import { Visibility, Organization, CurrentUser } from '../../app/types';
+import { isSonarCloud } from '../../helpers/system';
+import { isCurrentUserMemberOf, isPaidOrganization } from '../../helpers/organizations';
+import { getCurrentUser, getOrganizationByKey, getMyOrganizations } from '../../store/rootReducer';
+import VisibleIcon from '../icons-components/VisibleIcon';
+import DocTooltip from '../docs/DocTooltip';
+
+interface StateToProps {
+ currentUser: CurrentUser;
+ organization?: Organization;
+ userOrganizations: Organization[];
+}
+
+interface OwnProps {
+ className?: string;
+ organization: Organization | string | undefined;
+ qualifier: string;
+ tooltipProps?: { projectKey: string };
+ visibility: Visibility;
+}
+
+interface Props extends OwnProps, StateToProps {
+ organization: Organization | undefined;
+}
+
+export function PrivacyBadge({
+ className,
+ currentUser,
+ organization,
+ qualifier,
+ userOrganizations,
+ tooltipProps,
+ visibility
+}: Props) {
+ const onSonarCloud = isSonarCloud();
+ if (
+ visibility !== Visibility.Private &&
+ (!onSonarCloud || !isCurrentUserMemberOf(currentUser, organization, userOrganizations))
+ ) {
+ return null;
+ }
+
+ let icon = null;
+ if (isPaidOrganization(organization) && visibility === Visibility.Public) {
+ icon = <VisibleIcon className="little-spacer-right" fill={theme.blue} />;
+ }
+
+ const badge = (
+ <div
+ className={classNames('outline-badge', className, {
+ 'badge-info': Boolean(icon),
+ 'badge-icon': Boolean(icon)
+ })}>
+ {icon}
+ {translate('visibility', visibility)}
+ </div>
+ );
+
+ if (onSonarCloud && organization) {
+ let docUrl = `project/visibility-${visibility}`;
+ if (visibility === Visibility.Public) {
+ if (icon) {
+ docUrl += '-paid-org';
+ }
+ if (organization.canAdmin) {
+ docUrl += '-admin';
+ }
+ }
+
+ return (
+ <DocTooltip
+ className={className}
+ doc={docUrl}
+ overlayProps={{ ...tooltipProps, organization: organization.key }}>
+ {badge}
+ </DocTooltip>
+ );
+ }
+
+ return (
+ <Tooltip overlay={translate('visibility', visibility, 'description', qualifier)}>
+ {badge}
+ </Tooltip>
+ );
+}
+
+const mapStateToProps = (state: any, { organization }: OwnProps) => {
+ if (typeof organization === 'string') {
+ organization = getOrganizationByKey(state, organization);
+ }
+ return {
+ currentUser: getCurrentUser(state),
+ organization,
+ userOrganizations: getMyOrganizations(state)
+ };
+};
+
+export default connect<StateToProps, {}, OwnProps>(mapStateToProps)(PrivacyBadge);
+++ /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 * as classNames from 'classnames';
-import Tooltip from '../controls/Tooltip';
-import { translate } from '../../helpers/l10n';
-
-interface Props {
- className?: string;
- qualifier: string;
-}
-
-export default function PrivateBadge({ className, qualifier }: Props) {
- return (
- <Tooltip overlay={translate('visibility.private.description', qualifier)}>
- <div className={classNames('outline-badge', className)}>
- {translate('visibility.private')}
- </div>
- </Tooltip>
- );
-}
--- /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 { PrivacyBadge } from '../PrivacyBadgeContainer';
+import { Visibility, OrganizationSubscription } from '../../../app/types';
+import { isSonarCloud } from '../../../helpers/system';
+
+jest.mock('../../../helpers/system', () => ({ isSonarCloud: jest.fn().mockReturnValue(false) }));
+
+const organization = { key: 'foo', name: 'Foo' };
+const loggedInUser = { isLoggedIn: true, login: 'luke', name: 'Skywalker' };
+
+it('renders', () => {
+ expect(getWrapper()).toMatchSnapshot();
+});
+
+it('do not render', () => {
+ expect(getWrapper({ visibility: Visibility.Public })).toMatchSnapshot();
+});
+
+it('renders public', () => {
+ (isSonarCloud as jest.Mock<any>).mockReturnValueOnce(true);
+ expect(getWrapper({ visibility: Visibility.Public })).toMatchSnapshot();
+});
+
+it('renders public with icon', () => {
+ (isSonarCloud as jest.Mock<any>).mockReturnValueOnce(true);
+ expect(
+ getWrapper({
+ organization: {
+ ...organization,
+ canAdmin: true,
+ subscription: OrganizationSubscription.Paid
+ },
+ visibility: Visibility.Public
+ })
+ ).toMatchSnapshot();
+});
+
+function getWrapper(props = {}) {
+ return shallow(
+ <PrivacyBadge
+ currentUser={loggedInUser}
+ organization={organization}
+ qualifier="TRK"
+ userOrganizations={[organization]}
+ visibility={Visibility.Private}
+ {...props}
+ />
+ );
+}
+++ /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 PrivateBadge from '../PrivateBadge';
-
-it('renders', () => {
- expect(shallow(<PrivateBadge qualifier="TRK" />)).toMatchSnapshot();
-});
--- /dev/null
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`do not render 1`] = `""`;
+
+exports[`renders 1`] = `
+<Tooltip
+ overlay="visibility.private.description.TRK"
+>
+ <div
+ className="outline-badge"
+ >
+ visibility.private
+ </div>
+</Tooltip>
+`;
+
+exports[`renders public 1`] = `
+<DocTooltip
+ doc="project/visibility-public"
+ overlayProps={
+ Object {
+ "organization": "foo",
+ }
+ }
+>
+ <div
+ className="outline-badge"
+ >
+ visibility.public
+ </div>
+</DocTooltip>
+`;
+
+exports[`renders public with icon 1`] = `
+<DocTooltip
+ doc="project/visibility-public-paid-org-admin"
+ overlayProps={
+ Object {
+ "organization": "foo",
+ }
+ }
+>
+ <div
+ className="outline-badge badge-info badge-icon"
+ >
+ <VisibleIcon
+ className="little-spacer-right"
+ fill="#4b9fd5"
+ />
+ visibility.public
+ </div>
+</DocTooltip>
+`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<Tooltip
- overlay="visibility.private.description.TRK"
->
- <div
- className="outline-badge"
- >
- visibility.private
- </div>
-</Tooltip>
-`;
import { isSonarCloud } from '../../helpers/system';
interface Props {
+ childProps?: { [k: string]: string };
className?: string;
content: string | undefined;
displayH1?: boolean;
isTooltip?: boolean;
}
-export default function DocMarkdownBlock({ className, content, displayH1, isTooltip }: Props) {
+export default function DocMarkdownBlock({
+ childProps,
+ className,
+ content,
+ displayH1,
+ isTooltip
+}: Props) {
const parsed = separateFrontMatter(content || '');
return (
<div className={classNames('markdown', className)}>
// do not render outer <div />
div: React.Fragment,
// use custom link to render documentation anchors
- a: isTooltip ? DocTooltipLink : DocLink,
+ a: isTooltip ? withChildProps(DocTooltipLink, childProps) : DocLink,
// used to handle `@include`
p: DocParagraph,
// use custom img tag to render documentation images
);
}
+function withChildProps<P>(
+ WrappedComponent: React.ComponentType<P & { customProps?: { [k: string]: string } }>,
+ childProps?: { [k: string]: string }
+) {
+ return function withChildProps(props: P) {
+ return <WrappedComponent customProps={childProps} {...props} />;
+ };
+}
+
function filterContent(content: string) {
const beginning = isSonarCloud() ? '<!-- sonarqube -->' : '<!-- sonarcloud -->';
const ending = isSonarCloud() ? '<!-- /sonarqube -->' : '<!-- /sonarcloud -->';
children?: React.ReactNode;
/** Key of the documentation chunk */
doc: string;
+ overlayProps?: { [k: string]: string };
}
interface State {
{this.state.loading ? (
<i className="spinner" />
) : (
- <DocMarkdownBlock className="cut-margins" content={this.state.content} isTooltip={true} />
+ <DocMarkdownBlock
+ childProps={this.props.overlayProps}
+ className="cut-margins"
+ content={this.state.content}
+ isTooltip={true}
+ />
)}
</div>
);
*/
import * as React from 'react';
import { Link } from 'react-router';
+import { forEach } from 'lodash';
import DetachIcon from '../../components/icons-components/DetachIcon';
-export default function DocTooltipLink(props: React.AnchorHTMLAttributes<HTMLAnchorElement>) {
- const { children, href, ...other } = props;
+interface OwnProps {
+ customProps?: { [k: string]: string };
+}
+
+type Props = OwnProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;
+
+const SONARCLOUD_LINK = '/#sonarcloud#/';
+
+export default function DocTooltipLink({ children, customProps, href, ...other }: Props) {
+ if (customProps) {
+ forEach(customProps, (value, key) => {
+ if (href) {
+ href = href.replace(`#${key}#`, encodeURIComponent(value));
+ }
+ });
+ }
+
+ if (href && href.startsWith('/')) {
+ if (href.startsWith(SONARCLOUD_LINK)) {
+ href = `/${href.substr(SONARCLOUD_LINK.length)}`;
+ } else {
+ href = `/documentation/${href.substr(1)}`;
+ }
+
+ return (
+ <Link rel="noopener noreferrer" target="_blank" to={href} {...other}>
+ {children}
+ </Link>
+ );
+ }
+
return (
<>
- {href && href.startsWith('/') ? (
- <Link
- rel="noopener noreferrer"
- target="_blank"
- to={`/documentation/${href.substr(1)}`}
- {...other}>
- {children}
- </Link>
- ) : (
- <a href={href} rel="noopener noreferrer" target="_blank" {...other}>
- {children}
- </a>
- )}
+ <a href={href} rel="noopener noreferrer" target="_blank" {...other}>
+ {children}
+ </a>
<DetachIcon className="little-spacer-left little-spacer-right vertical-baseline" size={12} />
</>
);
(isSonarCloud as jest.Mock).mockImplementation(() => true);
expect(shallow(<DocMarkdownBlock content={content} />)).toMatchSnapshot();
});
+
+it('should render with custom props for links', () => {
+ expect(
+ shallow(
+ <DocMarkdownBlock
+ childProps={{ foo: 'bar' }}
+ content="some [link](#quality-profiles)"
+ isTooltip={true}
+ />
+ ).find('withChildProps')
+ ).toMatchSnapshot();
+});
it('should render internal link', () => {
expect(shallow(<DocTooltipLink href="/foo/bar" />)).toMatchSnapshot();
});
+
+it('should render links with custom props', () => {
+ expect(
+ shallow(<DocTooltipLink customProps={{ bar: 'baz' }} href="/#sonarcloud#/foo/#bar#" />)
+ ).toMatchSnapshot();
+});
link
</DocLink>
`;
+
+exports[`should render with custom props for links 1`] = `
+<withChildProps
+ href="#quality-profiles"
+ key="h-3"
+>
+ link
+</withChildProps>
+`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render internal link 1`] = `
-<React.Fragment>
- <Link
- onlyActiveOnIndex={false}
- rel="noopener noreferrer"
- style={Object {}}
- target="_blank"
- to="/documentation/foo/bar"
- />
- <DetachIcon
- className="little-spacer-left little-spacer-right vertical-baseline"
- size={12}
- />
-</React.Fragment>
+<Link
+ onlyActiveOnIndex={false}
+ rel="noopener noreferrer"
+ style={Object {}}
+ target="_blank"
+ to="/documentation/foo/bar"
+/>
+`;
+
+exports[`should render links with custom props 1`] = `
+<Link
+ onlyActiveOnIndex={false}
+ rel="noopener noreferrer"
+ style={Object {}}
+ target="_blank"
+ to="/foo/baz"
+/>
`;
exports[`should render simple link 1`] = `
--- /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 Icon, { IconProps } from './Icon';
+
+export default function VisibleIcon({ className, fill = 'currentColor', size }: IconProps) {
+ return (
+ <Icon className={className} size={size}>
+ <path
+ d="M13.524 8.403q-1.093-1.697-2.74-2.539 0.439 0.748 0.439 1.618 0 1.331-0.946 2.276t-2.276 0.946-2.276-0.946-0.946-2.276q0-0.87 0.439-1.618-1.647 0.842-2.74 2.539 0.957 1.474 2.399 2.348t3.125 0.874 3.125-0.874 2.399-2.348zM8.345 5.641q0-0.144-0.101-0.245t-0.245-0.101q-0.899 0-1.543 0.644t-0.644 1.543q0 0.144 0.101 0.245t0.245 0.101 0.245-0.101 0.101-0.245q0-0.619 0.439-1.057t1.057-0.439q0.144 0 0.245-0.101t0.101-0.245zM14.444 8.403q0 0.245-0.144 0.496-1.007 1.654-2.708 2.65t-3.593 0.996-3.593-1-2.708-2.647q-0.144-0.252-0.144-0.496t0.144-0.496q1.007-1.647 2.708-2.647t3.593-1 3.593 1 2.708 2.647q0.144 0.252 0.144 0.496z"
+ style={{ fill }}
+ />
+ </Icon>
+ );
+}
return Boolean(
organization &&
isLoggedIn(currentUser) &&
- (organization.canAdmin || userOrganizations.some(org => org.key === organization.key))
+ (organization.canAdmin ||
+ organization.isAdmin ||
+ userOrganizations.some(org => org.key === organization.key))
);
}
visibility.both=Public, Private
visibility.public=Public
-visibility.public.description=This project is public. Anyone can browse and see the source code.
+visibility.public.description.TRK=This project is public. Anyone can browse and see the source code.
+visibility.public.description.VW=This portfolio is public. Anyone can browse it.
+visibility.public.description.APP=This application is public. Anyone can browse it.
visibility.public.description.short=Anyone can browse and see the source code.
visibility.private=Private
visibility.private.description.TRK=This project is private. Only authorized users can browse and see the source code.
organization.members.members_groups={0}'s groups:
organization.members.manage_a_team=Manage a team
organization.members.add_to_members=Add to members
+organization.paid_plan.badge=Paid plan
organization.default_visibility_of_new_projects=Default visibility of new projects:
organization.change_visibility_form.header=Set Default Visibility of New Projects
organization.change_visibility_form.warning=This will not change the visibility of already existing projects.
organization.change_visibility_form.submit=Change Default Visibility
+
#------------------------------------------------------------------------------
#
# EMBEDED DOCS