* Use organizations actions instead of permissions flags from api/navigation/organization
import static org.sonar.db.Pagination.forPage;
import static org.sonar.db.organization.OrganizationQuery.newOrganizationQueryBuilder;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
+import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.Common.Paging;
.setInternal(true)
.setSince("6.2")
.setChangelog(new Change("6.4", "Paging fields have been added to the response"))
+ .setChangelog(new Change("7.5", "Removed 'isAdmin' and return 'actions' for each organization"))
.setHandler(this);
action.createParam(PARAM_ORGANIZATIONS)
Paging paging = buildWsPaging(request, total);
List<OrganizationDto> organizations = dbClient.organizationDao().selectByQuery(dbSession, dbQuery, forPage(paging.getPageIndex()).andSize(paging.getPageSize()));
Set<String> adminOrganizationUuids = searchOrganizationWithAdminPermission(dbSession);
+ Set<String> provisionOrganizationUuids = searchOrganizationWithProvisionPermission(dbSession);
Map<String, OrganizationAlmBindingDto> organizationAlmBindingByOrgUuid = dbClient.organizationAlmBindingDao().selectByOrganizations(dbSession, organizations)
.stream().collect(MoreCollectors.uniqueIndex(OrganizationAlmBindingDto::getOrganizationUuid));
- writeResponse(request, response, organizations, adminOrganizationUuids, organizationAlmBindingByOrgUuid, paging);
+ writeResponse(request, response, organizations, adminOrganizationUuids, provisionOrganizationUuids, organizationAlmBindingByOrgUuid, paging);
}
}
: dbClient.organizationDao().selectByPermission(dbSession, userId, ADMINISTER.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet());
}
- private static void writeResponse(Request httpRequest, Response httpResponse, List<OrganizationDto> organizations, Set<String> adminOrganizationUuids,
+ private Set<String> searchOrganizationWithProvisionPermission(DbSession dbSession) {
+ Integer userId = userSession.getUserId();
+ return userId == null ? emptySet()
+ : dbClient.organizationDao().selectByPermission(dbSession, userId, PROVISION_PROJECTS.getKey()).stream().map(OrganizationDto::getUuid).collect(toSet());
+ }
+
+ private void writeResponse(Request httpRequest, Response httpResponse, List<OrganizationDto> organizations,
+ Set<String> adminOrganizationUuids, Set<String> provisionOrganizationUuids,
Map<String, OrganizationAlmBindingDto> organizationAlmBindingByOrgUuid,
Paging paging) {
Organizations.SearchWsResponse.Builder response = Organizations.SearchWsResponse.newBuilder();
organizations
.forEach(o -> {
wsOrganization.clear();
- boolean isAdmin = adminOrganizationUuids.contains(o.getUuid());
- wsOrganization.setIsAdmin(isAdmin);
+ boolean isAdmin = userSession.isRoot() || adminOrganizationUuids.contains(o.getUuid());
+ boolean canProvision = userSession.isRoot() || provisionOrganizationUuids.contains(o.getUuid());
+ wsOrganization.setActions(Organization.Actions.newBuilder()
+ .setAdmin(isAdmin)
+ .setProvision(canProvision)
+ .setDelete(o.isGuarded() ? userSession.isRoot() : isAdmin));
response.addOrganizations(toOrganization(wsOrganization, o, organizationAlmBindingByOrgUuid.get(o.getUuid())));
});
writeProtobuf(response.build(), httpRequest, httpResponse);
import static org.sonar.db.organization.OrganizationDto.Subscription.PAID;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
-import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
import static org.sonar.server.ws.KeyExamples.KEY_ORG_EXAMPLE_001;
import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
.prop("isDefault", organization.getKey().equals(defaultOrganizationProvider.get().getKey()))
.prop("projectVisibility", Visibility.getLabel(newProjectPrivate))
.prop("subscription", organization.getSubscription().name())
- .prop("canAdmin", userSession.hasPermission(ADMINISTER, organization))
- .prop("canProvisionProjects", userSession.hasPermission(PROVISION_PROJECTS, organization))
- .prop("canDelete", organization.isGuarded() ? userSession.isSystemAdministrator() : userSession.hasPermission(ADMINISTER, organization))
.prop("canUpdateProjectsVisibilityToPrivate",
userSession.hasPermission(ADMINISTER, organization) &&
billingValidations.canUpdateProjectVisibilityToPrivate(new BillingValidations.Organization(organization.getKey(), organization.getUuid())));
"key": "foo-company",
"name": "Foo Company",
"guarded": true,
- "isAdmin": false
+ "actions": {
+ "admin": false,
+ "delete": false,
+ "provision": false
+ }
},
{
"key": "bar-company",
"url": "https://www.bar.com",
"avatar": "https://www.bar.com/logo.png",
"guarded": false,
- "isAdmin": true
+ "actions": {
+ "admin": true,
+ "delete": true,
+ "provision": false
+ }
}
]
}
{
"organization": {
- "canAdmin": true,
- "canProvisionProjects": true,
- "canDelete": false,
"pages": [
{
"key": "my-plugin/org-page",
import static org.sonar.db.organization.OrganizationDto.Subscription.FREE;
import static org.sonar.db.organization.OrganizationDto.Subscription.PAID;
import static org.sonar.db.permission.OrganizationPermission.ADMINISTER;
+import static org.sonar.db.permission.OrganizationPermission.PROVISION_PROJECTS;
import static org.sonar.server.organization.ws.SearchAction.PARAM_MEMBER;
import static org.sonar.test.JsonAssert.assertJson;
private WsActionTester ws = new WsActionTester(underTest);
@Test
- public void is_admin_available_for_each_organization() {
+ public void admin_and_delete_action_available_for_each_organization() {
OrganizationDto userAdminOrganization = db.organizations().insert();
OrganizationDto groupAdminOrganization = db.organizations().insert();
OrganizationDto browseOrganization = db.organizations().insert();
+ OrganizationDto guardedOrganization = db.organizations().insert(dto -> dto.setGuarded(true));
UserDto user = db.users().insertUser();
GroupDto group = db.users().insertGroup(groupAdminOrganization);
db.users().insertMember(group, user);
- userSession.logIn(user).addPermission(ADMINISTER, userAdminOrganization);
+ userSession.logIn(user).addPermission(ADMINISTER, userAdminOrganization)
+ .addPermission(ADMINISTER, guardedOrganization);
db.users().insertPermissionOnUser(userAdminOrganization, user, ADMINISTER);
+ db.users().insertPermissionOnUser(guardedOrganization, user, ADMINISTER);
db.users().insertPermissionOnGroup(group, ADMINISTER);
SearchWsResponse result = call(ws.newRequest());
- assertThat(result.getOrganizationsList()).extracting(Organization::getKey, Organization::getIsAdmin).containsExactlyInAnyOrder(
- tuple(userAdminOrganization.getKey(), true),
+ assertThat(result.getOrganizationsList())
+ .extracting(Organization::getKey, o -> o.getActions().getAdmin(), o -> o.getActions().getDelete())
+ .containsExactlyInAnyOrder(
+ tuple(userAdminOrganization.getKey(), true, true),
+ tuple(browseOrganization.getKey(), false, false),
+ tuple(groupAdminOrganization.getKey(), true, true),
+ tuple(guardedOrganization.getKey(), true, false));
+ }
+
+ @Test
+ public void root_can_do_everything() {
+ OrganizationDto organization = db.organizations().insert();
+ OrganizationDto guardedOrganization = db.organizations().insert(dto -> dto.setGuarded(true));
+ UserDto user = db.users().insertUser();
+ userSession.logIn(user).setRoot();
+
+ SearchWsResponse result = call(ws.newRequest());
+
+ assertThat(result.getOrganizationsList())
+ .extracting(Organization::getKey, o -> o.getActions().getAdmin(), o -> o.getActions().getDelete(), o -> o.getActions().getProvision())
+ .containsExactlyInAnyOrder(
+ tuple(organization.getKey(), true, true, true),
+ tuple(guardedOrganization.getKey(), true, true, true));
+ }
+
+ @Test
+ public void provision_action_available_for_each_organization() {
+ OrganizationDto userProvisionOrganization = db.organizations().insert();
+ OrganizationDto groupProvisionOrganization = db.organizations().insert();
+ OrganizationDto browseOrganization = db.organizations().insert();
+ UserDto user = db.users().insertUser();
+ GroupDto group = db.users().insertGroup(groupProvisionOrganization);
+ db.users().insertMember(group, user);
+ userSession.logIn(user).addPermission(PROVISION_PROJECTS, userProvisionOrganization);
+ db.users().insertPermissionOnUser(userProvisionOrganization, user, PROVISION_PROJECTS);
+ db.users().insertPermissionOnGroup(group, PROVISION_PROJECTS);
+
+ SearchWsResponse result = call(ws.newRequest());
+
+ assertThat(result.getOrganizationsList()).extracting(Organization::getKey, o -> o.getActions().getProvision()).containsExactlyInAnyOrder(
+ tuple(userProvisionOrganization.getKey(), true),
tuple(browseOrganization.getKey(), false),
- tuple(groupAdminOrganization.getKey(), true));
+ tuple(groupProvisionOrganization.getKey(), true));
}
@Test
.doesNotContain("my-plugin/org-admin-page");
}
- @Test
- public void returns_non_admin_and_canDelete_false_when_user_not_logged_in_and_key_is_the_default_organization() {
- TestResponse response = executeRequest(db.getDefaultOrganization());
-
- verifyResponse(response, false, false, false);
- }
-
- @Test
- public void returns_non_admin_and_canDelete_false_when_user_logged_in_but_not_admin_and_key_is_the_default_organization() {
- userSession.logIn();
-
- TestResponse response = executeRequest(db.getDefaultOrganization());
-
- verifyResponse(response, false, false, false);
- }
-
- @Test
- public void returns_admin_and_canDelete_true_when_user_logged_in_and_admin_and_key_is_the_default_organization() {
- OrganizationDto defaultOrganization = db.getDefaultOrganization();
- userSession.logIn().addPermission(ADMINISTER, defaultOrganization);
-
- TestResponse response = executeRequest(defaultOrganization);
-
- verifyResponse(response, true, false, true);
- }
-
- @Test
- public void returns_non_admin_and_canDelete_false_when_user_not_logged_in_and_key_is_not_the_default_organization() {
- OrganizationDto organization = db.organizations().insert();
- TestResponse response = executeRequest(organization);
-
- verifyResponse(response, false, false, false);
- }
-
- @Test
- public void returns_non_admin_and_canDelete_false_when_user_logged_in_but_not_admin_and_key_is_not_the_default_organization() {
- OrganizationDto organization = db.organizations().insert();
- userSession.logIn();
-
- TestResponse response = executeRequest(organization);
-
- verifyResponse(response, false, false, false);
- }
-
- @Test
- public void returns_admin_and_canDelete_true_when_user_logged_in_and_admin_and_key_is_not_the_default_organization() {
- OrganizationDto organization = db.organizations().insert();
- userSession.logIn().addPermission(ADMINISTER, organization);
-
- TestResponse response = executeRequest(organization);
-
- verifyResponse(response, true, false, true);
- }
-
- @Test
- public void returns_admin_and_canDelete_false_when_user_logged_in_and_admin_and_key_is_guarded_organization() {
- OrganizationDto organization = db.organizations().insert(dto -> dto.setGuarded(true));
- userSession.logIn().addPermission(ADMINISTER, organization);
-
- TestResponse response = executeRequest(organization);
-
- verifyResponse(response, true, false, false);
- }
-
- @Test
- public void returns_only_canDelete_true_when_user_is_system_administrator_and_key_is_guarded_organization() {
- OrganizationDto organization = db.organizations().insert(dto -> dto.setGuarded(true));
- userSession.logIn().setSystemAdministrator();
-
- TestResponse response = executeRequest(organization);
-
- verifyResponse(response, false, false, true);
- }
-
- @Test
- public void returns_provisioning_true_when_user_can_provision_projects_in_organization() {
- // user can provision projects in org2 but not in org1
- OrganizationDto org1 = db.organizations().insert();
- OrganizationDto org2 = db.organizations().insert();
- userSession.logIn().addPermission(PROVISION_PROJECTS, org2);
-
- verifyResponse(executeRequest(org1), false, false, false);
- verifyResponse(executeRequest(org2), false, true, false);
- }
-
@Test
public void returns_project_visibility_private() {
OrganizationDto organization = db.organizations().insert();
return request.execute();
}
- private static void verifyResponse(TestResponse response, boolean canAdmin, boolean canProvisionProjects, boolean canDelete) {
- assertJson(response.getInput())
- .isSimilarTo("{" +
- " \"organization\": {" +
- " \"canAdmin\": " + canAdmin + "," +
- " \"canProvisionProjects\": " + canProvisionProjects + "," +
- " \"canDelete\": " + canDelete +
- " \"pages\": []" +
- " }" +
- "}");
- }
-
private static void verifyCanUpdateProjectsVisibilityToPrivateResponse(TestResponse response, boolean canUpdateProjectsVisibilityToPrivate) {
assertJson(response.getInput())
.isSimilarTo("{" +
*/
import { getJSON, post, postJSON } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
-import { Organization, OrganizationBase, Paging, OrganizationMember } from '../app/types';
+import {
+ Organization,
+ OrganizationBase,
+ Paging,
+ OrganizationMember,
+ Extension
+} from '../app/types';
export function getOrganizations(data: {
organizations?: string;
}
interface GetOrganizationNavigation {
- adminPages: Array<{ key: string; name: string }>;
- canAdmin: boolean;
- canDelete: boolean;
- canProvisionProjects: boolean;
+ adminPages: Extension[];
+ canUpdateProjectsVisibilityToPrivate: boolean;
isDefault: boolean;
- pages: Array<{ key: string; name: string }>;
+ pages: Extension[];
}
export function getOrganizationNavigation(key: string): Promise<GetOrganizationNavigation> {
return null;
}
- let pages = organization.pages || [];
- if (organization.canAdmin && organization.adminPages) {
+ const { actions = {} } = organization;
+ let { pages = [] } = organization;
+ if (actions.admin && organization.adminPages) {
pages = pages.concat(organization.adminPages);
}
type: string;
}
+export interface OrganizationActions {
+ admin?: boolean;
+ delete?: boolean;
+ provision?: boolean;
+ executeAnalysis?: boolean;
+}
+
export interface Organization extends OrganizationBase {
+ actions?: OrganizationActions;
alm?: { key: string; url: string };
adminPages?: Extension[];
- canAdmin?: boolean;
- canDelete?: boolean;
- canProvisionProjects?: boolean;
canUpdateProjectsVisibilityToPrivate?: boolean;
guarded?: boolean;
- isAdmin?: boolean;
isDefault?: boolean;
key: string;
pages?: Extension[];
}
export default function OrganizationCard({ organization }: Props) {
+ const { actions = {} } = organization;
return (
<div className="account-project-card clearfix">
<aside className="account-project-side note">
<OrganizationLink className="spacer-left text-middle" organization={organization}>
{organization.name}
</OrganizationLink>
- {organization.isAdmin && (
- <span className="outline-badge spacer-left">{translate('admin')}</span>
- )}
+ {actions.admin && <span className="outline-badge spacer-left">{translate('admin')}</span>}
</h3>
{!!organization.description && (
createOrganization={this.props.createOrganization}
onOrgCreated={this.handleOrgCreated}
unboundOrganizations={this.props.userOrganizations.filter(
- o => !o.alm && o.key !== currentUser.personalOrganization
+ ({ actions = {}, alm, key }) =>
+ !alm && key !== currentUser.personalOrganization && actions.admin
)}
/>
)}
skipOnboardingAction={jest.fn()}
updateOrganization={jest.fn()}
userOrganizations={[
- { key: 'foo', name: 'Foo' },
- { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
+ { actions: { admin: true }, key: 'foo', name: 'Foo' },
+ { actions: { admin: true }, alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' },
+ { actions: { admin: false }, key: 'baz', name: 'Baz' }
]}
{...props}
/>
}
importPersonalOrg={
Object {
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
}
unboundOrganizations={
Array [
Object {
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
},
unboundOrganizations={
Array [
Object {
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
},
unboundOrganizations={
Array [
Object {
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
},
import Tabs from '../../../components/controls/Tabs';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations';
-import { skipOnboarding as skipOnboardingAction } from '../../../store/users';
+import { skipOnboarding } from '../../../store/users';
import { LoggedInUser, AlmApplication, Organization } from '../../../app/types';
import { getAlmAppInfo } from '../../../api/alm-integration';
-import { skipOnboarding } from '../../../api/users';
import { hasAdvancedALMIntegration } from '../../../helpers/almIntegrations';
import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
interface Props {
currentUser: LoggedInUser;
- skipOnboardingAction: () => void;
+ skipOnboarding: () => void;
userOrganizations: Organization[];
}
}
handleProjectCreate = (projectKeys: string[]) => {
- skipOnboarding().catch(() => {});
- this.props.skipOnboardingAction();
+ this.props.skipOnboarding();
if (projectKeys.length > 1) {
this.props.router.push({ pathname: '/projects' });
} else if (projectKeys.length === 1) {
currentUser={currentUser}
onProjectCreate={this.handleProjectCreate}
organization={state.organization}
- userOrganizations={userOrganizations}
+ userOrganizations={userOrganizations.filter(
+ ({ actions = {} }) => actions.provision
+ )}
/>
) : (
<AutoProjectCreate
almApplication={almApplication}
- boundOrganizations={userOrganizations.filter(o => o.alm)}
+ boundOrganizations={userOrganizations.filter(
+ ({ alm, actions = {} }) => alm && actions.provision
+ )}
onProjectCreate={this.handleProjectCreate}
organization={state.organization}
/>
}
}
-const mapDispatchToProps = { skipOnboardingAction };
+const mapDispatchToProps = { skipOnboarding };
export default whenLoggedIn(
withUserOrganizations(
// @ts-ignore avoid passing everything from WithRouterProps
location={{}}
router={mockRouter()}
- skipOnboardingAction={jest.fn()}
+ skipOnboarding={jest.fn()}
userOrganizations={[
- { key: 'foo', name: 'Foo' },
- { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
+ { actions: { provision: true }, key: 'foo', name: 'Foo' },
+ { actions: { provision: true }, alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' },
+ { actions: { provision: false }, key: 'baz', name: 'Baz' }
]}
{...props}
/>
boundOrganizations={
Array [
Object {
+ "actions": Object {
+ "provision": true,
+ },
"alm": Object {
"key": "github",
"url": "",
userOrganizations={
Array [
Object {
+ "actions": Object {
+ "provision": true,
+ },
"key": "foo",
"name": "Foo",
},
Object {
+ "actions": Object {
+ "provision": true,
+ },
"alm": Object {
"key": "github",
"url": "",
boundOrganizations={
Array [
Object {
+ "actions": Object {
+ "provision": true,
+ },
"alm": Object {
"key": "github",
"url": "",
render() {
const { member, organization } = this.props;
+ const { actions = {} } = organization;
return (
<tr>
<td className="thin nowrap">
<strong>{member.name}</strong>
<span className="note little-spacer-left">{member.login}</span>
</td>
- {organization.canAdmin && (
+ {actions.admin && (
<td className="text-right text-middle">
{translateWithParameters(
'organization.members.x_groups',
)}
</td>
)}
- {organization.canAdmin && (
+ {actions.admin && (
<React.Fragment>
<td className="nowrap text-middle text-right">
<ActionsDropdown>
componentDidMount() {
this.mounted = true;
this.fetchMembers();
- if (this.props.organization.canAdmin) {
+ if (this.props.organization.actions && this.props.organization.actions.admin) {
this.fetchGroups();
}
}
<Helmet title={translate('organization.members.page')} />
<Suggestions suggestions="organization_members" />
<MembersPageHeader loading={loading}>
- {organization.canAdmin && (
- <div className="page-actions">
- <AddMemberForm
- addMember={this.handleAddMember}
- memberLogins={memberLogins}
- organization={organization}
- />
- <DocTooltip
- className="spacer-left"
- doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md')}
- />
- </div>
- )}
+ {organization.actions &&
+ organization.actions.admin && (
+ <div className="page-actions">
+ <AddMemberForm
+ addMember={this.handleAddMember}
+ memberLogins={memberLogins}
+ organization={organization}
+ />
+ <DocTooltip
+ className="spacer-left"
+ doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md')}
+ />
+ </div>
+ )}
</MembersPageHeader>
{members !== undefined &&
paging !== undefined && (
const wrapper = shallow(
<MembersListItem
member={admin}
- organization={{ ...organization, canAdmin: true }}
+ organization={{ ...organization, actions: { admin: true } }}
organizationGroups={[]}
removeMember={jest.fn()}
updateMemberGroups={jest.fn()}
const wrapper = shallow(
<MembersListItem
member={john}
- organization={{ ...organization, canAdmin: true }}
+ organization={{ ...organization, actions: { admin: true } }}
organizationGroups={[]}
removeMember={jest.fn()}
updateMemberGroups={jest.fn()}
const wrapper = shallow(
<MembersListItem
member={admin}
- organization={{ ...organization, canAdmin: true }}
+ organization={{ ...organization, actions: { admin: true } }}
organizationGroups={[]}
removeMember={jest.fn()}
updateMemberGroups={jest.fn()}
const wrapper = shallow(
<MembersListItem
member={admin}
- organization={{ ...organization, canAdmin: true }}
+ organization={{ ...organization, actions: { admin: true } }}
organizationGroups={[]}
removeMember={jest.fn()}
updateMemberGroups={jest.fn()}
it('should fetch members and groups and render for admin', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
it('should search users', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
wrapper.find('MembersListHeader').prop<Function>('handleSearch')('user');
it('should load more members', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
wrapper.find('ListFooter').prop<Function>('loadMore')();
it('should add new member', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
wrapper.find('AddMemberForm').prop<Function>('addMember')({ login: 'bar' });
it('should remove member', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
wrapper.find('MembersList').prop<Function>('removeMember')({ login: 'john' });
it('should update groups', async () => {
const wrapper = shallow(
- <OrganizationMembers organization={{ ...organization, canAdmin: true }} />
+ <OrganizationMembers organization={{ ...organization, actions: { admin: true } }} />
);
await waitAndUpdate(wrapper);
wrapper.find('MembersList').prop<Function>('updateMemberGroups')(
}
organization={
Object {
- "canAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
}
}
organization={
Object {
- "canAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
}
currentUser,
organization
}: Pick<StateToProps, 'currentUser' | 'organization'>) {
- const isAdmin = isLoggedIn(currentUser) && organization && organization.canAdmin;
- return Boolean(isAdmin);
+ return Boolean(
+ isLoggedIn(currentUser) && organization && organization.actions && organization.actions.admin
+ );
}
export function OrganizationAdminAccess(props: OwnProps) {
render() {
const { organization } = this.props;
- if (!organization || organization.canAdmin == null) {
+ if (!organization || !organization.actions || organization.actions.admin == null) {
if (this.state.loading) {
return null;
} else {
};
const organization = {
- canAdmin: false,
+ actions: { admin: false },
key: 'foo',
name: 'Foo',
projectVisibility: Visibility.Public
};
-const adminOrganization = { ...organization, canAdmin: true };
+const adminOrganization = { ...organization, actions: { admin: true } };
describe('component', () => {
it('should render children', () => {
const wrapper = getWrapper();
expect(wrapper.type()).toBeNull();
- const organization = { key: 'foo', name: 'Foo', isDefault: false, canAdmin: false };
+ const organization = { actions: { admin: false }, key: 'foo', name: 'Foo', isDefault: false };
wrapper.setProps({ organization });
expect(wrapper).toMatchSnapshot();
});
location={Object {}}
organization={
Object {
- "canAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
"projectVisibility": "public",
}
organization={
Object {
- "canAdmin": false,
+ "actions": Object {
+ "admin": false,
+ },
"isDefault": false,
"key": "foo",
"name": "Foo",
];
export default function OrganizationNavigationAdministration({ location, organization }: Props) {
- const extensions = organization.adminPages || [];
- const adminPathsWithExtensions = extensions.map(e => `extension/${e.key}`).concat(ADMIN_PATHS);
+ const { actions = {}, adminPages = [] } = organization;
+ const adminPathsWithExtensions = adminPages.map(e => `extension/${e.key}`).concat(ADMIN_PATHS);
const adminActive = adminPathsWithExtensions.some(path =>
location.pathname.endsWith(`organizations/${organization.key}/${path}`)
);
<Dropdown
overlay={
<ul className="menu">
- {extensions.map(extension => (
+ {adminPages.map(extension => (
<li key={extension.key}>
<Link
activeClassName="active"
{translate('edit')}
</Link>
</li>
- {organization.canDelete && (
+ {actions.delete && (
<li>
<Link activeClassName="active" to={`/organizations/${organization.key}/delete`}>
{translate('delete')}
userOrganizations
}: Props) {
const hasPrivateRights = hasPrivateAccess(currentUser, organization, userOrganizations);
+ const { actions = {} } = organization;
return (
<NavBarTabs className="navbar-context-tabs">
<li>
</li>
)}
<OrganizationNavigationExtensions location={location} organization={organization} />
- {organization.canAdmin && (
+ {actions.admin && (
<OrganizationNavigationAdministration location={location} organization={organization} />
)}
</NavBarTabs>
it('renders dropdown', () => {
const organizations = [
- { isAdmin: true, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
- { isAdmin: false, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
+ { actions: { admin: true }, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
+ { actions: { admin: false }, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
];
const wrapper = shallow(
<OrganizationNavigationHeader
<OrganizationNavigationMenu
currentUser={loggedInUser}
location={{ pathname: '' }}
- organization={{ ...organization, canAdmin: true }}
+ organization={{ ...organization, actions: { admin: true } }}
userOrganizations={[organization]}
/>
)
<OrganizationListItem
organization={
Object {
- "isAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "org1",
"name": "org1",
"projectVisibility": "public",
<OrganizationListItem
organization={
Object {
- "isAdmin": false,
+ "actions": Object {
+ "admin": false,
+ },
"key": "org2",
"name": "org2",
"projectVisibility": "public",
}
organization={
Object {
- "canAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
"projectVisibility": "public",
}
organization={
Object {
- "canAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "Foo",
"projectVisibility": "public",
render() {
const { currentUser, organization } = this.props;
const showNewProjectButton = isSonarCloud()
- ? organization && organization.canProvisionProjects
+ ? organization && organization.actions && organization.actions.provision
: isLoggedIn(currentUser) && hasGlobalPermission(currentUser, 'provisioning');
return (
shallow(
<EmptyInstance
currentUser={{ isLoggedIn: false }}
- organization={{ canProvisionProjects: true, key: 'foo', name: 'Foo' }}
+ organization={{ actions: { provision: true }, key: 'foo', name: 'Foo' }}
/>
)
).toMatchSnapshot();
it('renders for SonarCloud', () => {
(isSonarCloud as jest.Mock).mockImplementation(() => true);
const organizations = [
- { isAdmin: true, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
- { isAdmin: false, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
+ { actions: { admin: true }, key: 'org1', name: 'org1', projectVisibility: Visibility.Public },
+ { actions: { admin: false }, key: 'org2', name: 'org2', projectVisibility: Visibility.Public }
];
expect(shallow(<NoFavoriteProjects organizations={organizations} />)).toMatchSnapshot();
});
<OrganizationListItem
organization={
Object {
- "isAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "org1",
"name": "org1",
"projectVisibility": "public",
<OrganizationListItem
organization={
Object {
- "isAdmin": false,
+ "actions": Object {
+ "admin": false,
+ },
"key": "org2",
"name": "org2",
"projectVisibility": "public",
}
const topLevelQualifiers = organization.isDefault ? this.props.appState.qualifiers : ['TRK'];
+ const { actions = {} } = organization;
return (
<App
currentUser={this.props.currentUser}
- hasProvisionPermission={organization.canProvisionProjects}
+ hasProvisionPermission={actions.provision}
onVisibilityChange={this.handleVisibilityChange}
organization={organization}
topLevelQualifiers={topLevelQualifiers}
*/
import * as React from 'react';
import * as classNames from 'classnames';
-import { Organization, Visibility } from '../../app/types';
import UpgradeOrganizationBox from '../../components/common/UpgradeOrganizationBox';
import Modal from '../../components/controls/Modal';
+import { Organization, Visibility } from '../../app/types';
import { Button, ResetButtonLink } from '../../components/ui/buttons';
import { translate } from '../../helpers/l10n';
import { Alert } from '../../components/ui/Alert';
};
render() {
- const { canUpdateProjectsVisibilityToPrivate } = this.props.organization;
-
+ const { organization } = this.props;
return (
<Modal contentLabel="modal form" onRequestClose={this.props.onClose}>
<header className="modal-head">
{[Visibility.Public, Visibility.Private].map(visibility => (
<div className="big-spacer-bottom" key={visibility}>
<p>
- {visibility === Visibility.Private && !canUpdateProjectsVisibilityToPrivate ? (
+ {visibility === Visibility.Private &&
+ !organization.canUpdateProjectsVisibilityToPrivate ? (
<span className="text-muted cursor-not-allowed">
<i
className={classNames('icon-radio', 'spacer-right', {
</div>
))}
- {canUpdateProjectsVisibilityToPrivate ? (
+ {organization.canUpdateProjectsVisibilityToPrivate ? (
<Alert variant="warning">
{translate('organization.change_visibility_form.warning')}
</Alert>
*/
import * as React from 'react';
import { shallow } from 'enzyme';
-import ChangeVisibilityForm, { Props } from '../ChangeVisibilityForm';
+import ChangeVisibilityForm from '../ChangeVisibilityForm';
import { click } from '../../../helpers/testUtils';
import { Visibility } from '../../../app/types';
expect(onConfirm).toBeCalledWith(Visibility.Private);
});
-function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
+function shallowRender(props: Partial<ChangeVisibilityForm['props']> = {}) {
return shallow(
<ChangeVisibilityForm
onClose={jest.fn()}
getOrganizations({ member: true }).then(
({ organizations }) => {
if (this.mounted) {
- const organizationKeys = organizations.filter(o => o.isAdmin).map(o => o.key);
+ const organizationKeys = organizations
+ .filter(o => o.actions && o.actions.admin)
+ .map(o => o.key);
// best guess: if there is only one organization, then it is personal
// otherwise, we can't guess, let's display them all as just "existing organizations"
const personalOrganization =
jest.mock('../../../../api/organizations', () => ({
getOrganizations: jest.fn(() =>
Promise.resolve({
- organizations: [{ isAdmin: true, key: 'user' }, { isAdmin: true, key: 'another' }]
+ organizations: [
+ { actions: { admin: true }, key: 'user' },
+ { actions: { admin: true }, key: 'another' }
+ ]
})
)
}));
function getDoc(visibility: Visibility, icon: JSX.Element | null, organization: Organization) {
let doc;
- const canAdmin = organization.canAdmin || organization.isAdmin;
+ const { actions = {} } = organization;
if (visibility === Visibility.Private) {
doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-private.md');
} else if (icon) {
- if (canAdmin) {
+ if (actions.admin) {
doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-paid-org-admin.md');
} else {
doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-paid-org.md');
}
- } else if (canAdmin) {
+ } else if (actions.admin) {
doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public-admin.md');
} else {
doc = import(/* webpackMode: "eager" */ 'Docs/tooltips/project/visibility-public.md');
getWrapper({
organization: {
...organization,
- canAdmin: true,
+ actions: { admin: true },
subscription: OrganizationSubscription.Paid
},
visibility: Visibility.Public
}
export default function OrganizationListItem({ organization }: Props) {
+ const { actions = {} } = organization;
return (
<li>
<OrganizationLink className="display-flex-center" organization={organization}>
<OrganizationAvatar organization={organization} small={true} />
<span className="spacer-left">{organization.name}</span>
</div>
- {organization.isAdmin && (
- <span className="outline-badge spacer-left">{translate('admin')}</span>
- )}
+ {actions.admin && <span className="outline-badge spacer-left">{translate('admin')}</span>}
</OrganizationLink>
</li>
);
shallow(
<OrganizationListItem
organization={{
- isAdmin: true,
+ actions: { admin: true },
key: 'org',
name: 'org',
projectVisibility: Visibility.Public
className="display-flex-center"
organization={
Object {
- "isAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "org",
"name": "org",
"projectVisibility": "public",
<OrganizationAvatar
organization={
Object {
- "isAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "org",
"name": "org",
"projectVisibility": "public",
import { OrganizationSubscription } from '../../app/types';
const org = { key: 'foo', name: 'Foo', subscription: OrganizationSubscription.Paid };
-const adminOrg = { key: 'bar', name: 'Bar', canAdmin: true };
+const adminOrg = { actions: { admin: true }, key: 'bar', name: 'Bar' };
const randomOrg = { key: 'bar', name: 'Bar' };
const loggedIn = {
return Boolean(
organization &&
isLoggedIn(currentUser) &&
- (organization.canAdmin ||
- organization.isAdmin ||
+ ((organization.actions && organization.actions.admin) ||
userOrganizations.some(org => org.key === organization.key))
);
}
Object {
"byKey": Object {
"foo": Object {
- "isAdmin": true,
+ "actions": Object {
+ "admin": true,
+ },
"key": "foo",
"name": "foo",
},
Object {
"byKey": Object {
"foo": Object {
+ "actions": Object {
+ "admin": true,
+ },
"description": "description",
- "isAdmin": true,
"key": "foo",
"name": "bar",
},
case 'RECEIVE_MY_ORGANIZATIONS':
return onReceiveOrganizations(state, action);
case 'CREATE_ORGANIZATION':
- return { ...state, [action.organization.key]: { ...action.organization, isAdmin: true } };
+ return {
+ ...state,
+ [action.organization.key]: {
+ ...action.organization,
+ actions: { ...(action.organization.actions || {}), admin: true }
+ }
+ };
case 'UPDATE_ORGANIZATION':
return {
...state,
optional string url = 4;
optional string avatar = 5;
optional bool guarded = 6;
- optional bool isAdmin = 7;
optional Alm alm = 8;
+ optional Actions actions = 9;
message Alm {
optional string key = 1;
optional string url = 2;
}
+
+ message Actions {
+ optional bool admin = 1;
+ optional bool delete = 2;
+ optional bool provision = 3;
+ }
}
message User {