Browse Source

SONAR-9000 display all organizations a user is a member of

tags/7.0-RC1
Stas Vilchik 6 years ago
parent
commit
d1157c4101

+ 10
- 10
server/sonar-web/src/main/js/api/organizations.ts View File

@@ -20,11 +20,17 @@
import { getJSON, post, postJSON, RequestData } from '../helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';

interface GetOrganizationsParameters {
organizations?: string;
member?: boolean;
}

interface GetOrganizationsResponse {
organizations: Array<{
avatar?: string;
description?: string;
guarded: boolean;
isAdmin: boolean;
key: string;
name: string;
url?: string;
@@ -36,20 +42,14 @@ interface GetOrganizationsResponse {
};
}

export function getOrganizations(organizations?: string[]): Promise<GetOrganizationsResponse> {
const data: RequestData = {};
if (organizations) {
Object.assign(data, { organizations: organizations.join() });
}
export function getOrganizations(
data: GetOrganizationsParameters
): Promise<GetOrganizationsResponse> {
return getJSON('/api/organizations/search', data);
}

export function getMyOrganizations(): Promise<any> {
return getJSON('/api/organizations/search_my_organizations').then(r => r.organizations);
}

export function getOrganization(key: string): Promise<any> {
return getOrganizations([key])
return getOrganizations({ organizations: key })
.then(r => r.organizations.find((o: any) => o.key === key))
.catch(throwGlobalError);
}

+ 12
- 4
server/sonar-web/src/main/js/app/components/nav/global/GlobalNavUser.js View File

@@ -44,7 +44,7 @@ type Props = {
currentUser: CurrentUser,
fetchMyOrganizations: () => Promise<*>,
location: Object,
organizations: Array<{ key: string, name: string }>,
organizations: Array<{ isAdmin: bool, key: string, name: string }>,
router: { push: string => void }
};
*/
@@ -157,9 +157,17 @@ export default class GlobalNavUser extends React.PureComponent {
{hasOrganizations &&
sortBy(organizations, org => org.name.toLowerCase()).map(organization => (
<li key={organization.key}>
<OrganizationLink organization={organization} onClick={this.closeDropdown}>
<OrganizationIcon />
<span className="spacer-left">{organization.name}</span>
<OrganizationLink
className="dropdown-item-flex"
organization={organization}
onClick={this.closeDropdown}>
<div>
<OrganizationIcon />
<span className="spacer-left">{organization.name}</span>
</div>
{organization.isAdmin && (
<span className="outline-badge spacer-left">{translate('admin')}</span>
)}
</OrganizationLink>
</li>
))}

+ 27
- 18
server/sonar-web/src/main/js/app/components/nav/global/__tests__/__snapshots__/GlobalNavUser-test.js.snap View File

@@ -201,6 +201,7 @@ exports[`should render the users organizations 1`] = `
key="bar"
>
<OrganizationLink
className="dropdown-item-flex"
onClick={[Function]}
organization={
Object {
@@ -209,18 +210,21 @@ exports[`should render the users organizations 1`] = `
}
}
>
<OrganizationIcon />
<span
className="spacer-left"
>
bar
</span>
<div>
<OrganizationIcon />
<span
className="spacer-left"
>
bar
</span>
</div>
</OrganizationLink>
</li>
<li
key="foo"
>
<OrganizationLink
className="dropdown-item-flex"
onClick={[Function]}
organization={
Object {
@@ -229,18 +233,21 @@ exports[`should render the users organizations 1`] = `
}
}
>
<OrganizationIcon />
<span
className="spacer-left"
>
Foo
</span>
<div>
<OrganizationIcon />
<span
className="spacer-left"
>
Foo
</span>
</div>
</OrganizationLink>
</li>
<li
key="myorg"
>
<OrganizationLink
className="dropdown-item-flex"
onClick={[Function]}
organization={
Object {
@@ -249,12 +256,14 @@ exports[`should render the users organizations 1`] = `
}
}
>
<OrganizationIcon />
<span
className="spacer-left"
>
MyOrg
</span>
<div>
<OrganizationIcon />
<span
className="spacer-left"
>
MyOrg
</span>
</div>
</OrganizationLink>
</li>
<li

+ 6
- 0
server/sonar-web/src/main/js/app/styles/components/dropdowns.css View File

@@ -176,3 +176,9 @@
color: var(--secondFontColor);
font-size: 11px;
}

.dropdown-item-flex {
display: flex !important;
justify-content: space-between;
align-items: center;
}

+ 3
- 0
server/sonar-web/src/main/js/apps/account/organizations/OrganizationCard.js View File

@@ -51,6 +51,9 @@ export default function OrganizationCard(props /*: Props */) {

<h3 className="account-project-name">
<OrganizationLink organization={organization}>{organization.name}</OrganizationLink>
{organization.isAdmin && (
<span className="outline-badge spacer-left">{translate('admin')}</span>
)}
</h3>

{!!organization.description && (

+ 3
- 9
server/sonar-web/src/main/js/apps/account/organizations/actions.js View File

@@ -17,20 +17,14 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as api from '../../../api/organizations';
import { getOrganizations } from '../../../api/organizations';
import { receiveMyOrganizations } from '../../../store/organizations/duck';
import { getValues } from '../../../api/settings';
import { receiveValues } from '../../settings/store/values/actions';

export const fetchMyOrganizations = () => dispatch => {
return api.getMyOrganizations().then(keys => {
if (keys.length > 0) {
return api.getOrganizations(keys).then(({ organizations }) => {
return dispatch(receiveMyOrganizations(organizations));
});
} else {
return dispatch(receiveMyOrganizations([]));
}
return getOrganizations({ member: true }).then(({ organizations }) => {
return dispatch(receiveMyOrganizations(organizations));
});
};


+ 1
- 1
server/sonar-web/src/main/js/apps/issues/components/AppContainer.js View File

@@ -41,7 +41,7 @@ const fetchIssueOrganizations = issues => dispatch => {
}

const organizationKeys = uniq(issues.map(issue => issue.organization));
return getOrganizations(organizationKeys).then(
return getOrganizations({ organizations: organizationKeys.join() }).then(
response => dispatch(receiveOrganizations(response.organizations)),
throwGlobalError
);

+ 1
- 1
server/sonar-web/src/main/js/apps/projects/utils.ts View File

@@ -270,7 +270,7 @@ function fetchProjectOrganizations(projects: Array<{ organization: string }>) {
}

const organizations = uniq(projects.map(project => project.organization));
return getOrganizations(organizations).then(r => r.organizations);
return getOrganizations({ organizations: organizations.join() }).then(r => r.organizations);
}

function mapFacetValues(values: Array<{ val: string; count: number }>) {

+ 7
- 5
server/sonar-web/src/main/js/apps/tutorials/onboarding/OrganizationStep.js View File

@@ -23,7 +23,7 @@ import classNames from 'classnames';
import { sortBy } from 'lodash';
import Step from './Step';
import NewOrganizationForm from './NewOrganizationForm';
import { getMyOrganizations } from '../../../api/organizations';
import { getOrganizations } from '../../../api/organizations';
import Select from '../../../components/controls/Select';
import { translate } from '../../../helpers/l10n';

@@ -68,13 +68,15 @@ export default class OrganizationStep extends React.PureComponent {
}

fetchOrganizations = () => {
getMyOrganizations().then(
organizations => {
getOrganizations({ member: true }).then(
({ organizations }) => {
if (this.mounted) {
const organizationKeys = organizations.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 = organizations.length === 1 ? organizations[0] : undefined;
const existingOrganizations = organizations.length > 1 ? sortBy(organizations) : [];
const personalOrganization =
organizationKeys.length === 1 ? organizationKeys[0] : undefined;
const existingOrganizations = organizationKeys.length > 1 ? sortBy(organizationKeys) : [];
const selection = personalOrganization
? 'personal'
: existingOrganizations.length > 0 ? 'existing' : 'new';

+ 5
- 3
server/sonar-web/src/main/js/apps/tutorials/onboarding/__tests__/OrganizationStep-test.js View File

@@ -22,16 +22,18 @@ import React from 'react';
import { mount } from 'enzyme';
import OrganizationStep from '../OrganizationStep';
import { click } from '../../../../helpers/testUtils';
import { getMyOrganizations } from '../../../../api/organizations';
import { getOrganizations } from '../../../../api/organizations';

jest.mock('../../../../api/organizations', () => ({
getMyOrganizations: jest.fn(() => Promise.resolve(['user', 'another']))
getOrganizations: jest.fn(() =>
Promise.resolve({ organizations: [{ key: 'user' }, { key: 'another' }] })
)
}));

const currentUser = { isLoggedIn: true, login: 'user' };

beforeEach(() => {
getMyOrganizations.mockClear();
getOrganizations.mockClear();
});

// FIXME

+ 1
- 0
server/sonar-web/src/main/js/store/organizations/duck.js View File

@@ -30,6 +30,7 @@ export type Organization = {
canProvisionProjects?: boolean,
canUpdateProjectsVisibilityToPrivate?: boolean,
description?: string,
isAdmin: bool,
key: string,
name: string,
pages?: Array<{ key: string, name: string }>,

+ 1
- 1
server/sonar-web/src/main/js/store/rootActions.js View File

@@ -45,7 +45,7 @@ export const fetchMetrics = () => dispatch =>
getAllMetrics().then(metrics => dispatch(receiveMetrics(metrics)), onFail(dispatch));

export const fetchOrganizations = (organizations /*: Array<string> | void */) => dispatch =>
getOrganizations(organizations).then(
getOrganizations({ organizations: organizations && organizations.join() }).then(
r => dispatch(receiveOrganizations(r.organizations)),
onFail(dispatch)
);

+ 1
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -8,6 +8,7 @@ action=Action
actions=Actions
active=Active
add_verb=Add
admin=Admin
apply=Apply
all=All
and=And

Loading…
Cancel
Save