Skip to content

Commit

Permalink
SONAR-11321 Create organization from GitHub organization or BitBucket…
Browse files Browse the repository at this point in the history
… team

* Create api/alm_integration/show_organization and handle only GitHub installation
* Add import from ALM tab in Create Org page
* Do not show error while validating detail input
* Add step to create organization from ALM
* Display a warning if the installation id was not found
* Add Alm link to remote organization in org context
* Create GET api/alm_integration/show_app_info
  • Loading branch information
julienlancelot authored and SonarTech committed Nov 16, 2018
1 parent f8694e7 commit f61d654
Show file tree
Hide file tree
Showing 78 changed files with 1,892 additions and 418 deletions.
Expand Up @@ -52,6 +52,11 @@ public Optional<AlmAppInstallDto> selectByOwner(DbSession dbSession, ALM alm, St
return Optional.ofNullable(mapper.selectByOwner(alm.getId(), ownerId));
}

public Optional<String> getOwerId(DbSession dbSession, ALM alm, String installationId) {
AlmAppInstallMapper mapper = getMapper(dbSession);
return Optional.ofNullable(mapper.selectOwnerId(alm.getId(), installationId));
}

public List<AlmAppInstallDto> findAllWithNoOwnerType(DbSession dbSession) {
return getMapper(dbSession).selectAllWithNoOwnerType();
}
Expand Down
Expand Up @@ -29,6 +29,9 @@ public interface AlmAppInstallMapper {
@CheckForNull
AlmAppInstallDto selectByOwner(@Param("almId") String almId, @Param("ownerId") String ownerId);

@CheckForNull
String selectOwnerId(@Param("almId") String almId, @Param("installId") String installId);

List<AlmAppInstallDto> selectAllWithNoOwnerType();

void insert(@Param("uuid") String uuid, @Param("almId") String almId, @Param("ownerId") String ownerId,
Expand Down
Expand Up @@ -22,6 +22,16 @@
and owner_id = #{ownerId, jdbcType=VARCHAR}
</select>

<select id="selectOwnerId" parameterType="Map" resultType="String">
select
owner_id as ownerId
from
alm_app_installs
where
alm_id = #{almId, jdbcType=VARCHAR}
and install_id = #{installId, jdbcType=VARCHAR}
</select>

<select id="selectAllWithNoOwnerType" parameterType="Map" resultType="org.sonar.db.alm.AlmAppInstallDto">
select <include refid="sqlColumns" />
from
Expand Down
Expand Up @@ -72,7 +72,6 @@ public void selectByOwner() {
assertThat(underTest.selectByOwner(dbSession, BITBUCKETCLOUD, A_OWNER)).isEmpty();
}


@Test
public void selectByOwner_throws_NPE_when_alm_is_null() {
expectAlmNPE();
Expand All @@ -94,6 +93,16 @@ public void selectByOwner_throws_IAE_when_owner_id_is_empty() {
underTest.selectByOwner(dbSession, GITHUB, EMPTY_STRING);
}

@Test
public void getOwnerId() {
when(uuidFactory.create()).thenReturn(A_UUID);
underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, AN_INSTALL);

assertThat(underTest.getOwerId(dbSession, GITHUB, AN_INSTALL)).contains(A_OWNER);
assertThat(underTest.getOwerId(dbSession, GITHUB, "unknown")).isEmpty();
assertThat(underTest.getOwerId(dbSession, BITBUCKETCLOUD, AN_INSTALL)).isEmpty();
}

@Test
public void insert_throws_NPE_if_alm_is_null() {
expectAlmNPE();
Expand Down Expand Up @@ -170,7 +179,7 @@ public void update() {
underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, AN_INSTALL);

when(system2.now()).thenReturn(DATE_LATER);
underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER, true, OTHER_INSTALL);
underTest.insertOrUpdate(dbSession, GITHUB, A_OWNER,true, OTHER_INSTALL);

assertThatAlmAppInstall(GITHUB, A_OWNER)
.hasInstallId(OTHER_INSTALL)
Expand Down
Expand Up @@ -38,33 +38,23 @@

import static java.lang.String.format;
import static org.apache.commons.lang.StringUtils.defaultString;
import static org.sonar.server.user.UserSession.IdentityProvider.SONARQUBE;

public abstract class AbstractUserSession implements UserSession {
private static final Set<String> PUBLIC_PERMISSIONS = ImmutableSet.of(UserRole.USER, UserRole.CODEVIEWER);
private static final String INSUFFICIENT_PRIVILEGES_MESSAGE = "Insufficient privileges";
private static final String AUTHENTICATION_IS_REQUIRED_MESSAGE = "Authentication is required";

protected static Identity computeIdentity(UserDto userDto) {
switch (userDto.getExternalIdentityProvider()) {
case "github":
return new Identity(IdentityProvider.GITHUB, externalIdentityOf(userDto));
case "bitbucket":
return new Identity(IdentityProvider.BITBUCKET, externalIdentityOf(userDto));
case "sonarqube":
return new Identity(IdentityProvider.SONARQUBE, null);
default:
return new Identity(IdentityProvider.OTHER, externalIdentityOf(userDto));
}
IdentityProvider identityProvider = IdentityProvider.getFromKey(userDto.getExternalIdentityProvider());
ExternalIdentity externalIdentity = identityProvider == SONARQUBE ? null : externalIdentityOf(userDto);
return new Identity(identityProvider, externalIdentity);
}

@CheckForNull
private static ExternalIdentity externalIdentityOf(UserDto userDto) {
String externalId = userDto.getExternalId();
String externalLogin = userDto.getExternalLogin();
if (externalId == null && externalLogin == null) {
return null;
}
return new ExternalIdentity(externalId == null ? externalLogin : externalId, externalLogin);
return new ExternalIdentity(externalId, externalLogin);
}

protected static final class Identity {
Expand Down
Expand Up @@ -19,6 +19,7 @@
*/
package org.sonar.server.user;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
Expand Down Expand Up @@ -72,7 +73,24 @@ public interface UserSession {
* This enum supports by name only the few providers for which specific code exists.
*/
enum IdentityProvider {
SONARQUBE, GITHUB, BITBUCKET, OTHER
SONARQUBE("sonarqube"), GITHUB("github"), BITBUCKET("bitbucket"), OTHER("other");

String key;

IdentityProvider(String key) {
this.key = key;
}

public String getKey() {
return key;
}

public static IdentityProvider getFromKey(String key) {
return Arrays.stream(IdentityProvider.values())
.filter(i -> i.getKey().equals(key))
.findAny()
.orElse(OTHER);
}
}

/**
Expand Down
16 changes: 15 additions & 1 deletion server/sonar-web/src/main/js/api/alm-integration.ts
Expand Up @@ -18,9 +18,23 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getJSON, postJSON } from '../helpers/request';
import { AlmRepository } from '../app/types';
import { AlmRepository, AlmApplication, AlmOrganization } from '../app/types';
import throwGlobalError from '../app/utils/throwGlobalError';

export function getAlmAppInfo(): Promise<{ application: AlmApplication }> {
return getJSON('/api/alm_integration/show_app_info').catch(throwGlobalError);
}

export function getAlmOrganization(data: { installationId: string }): Promise<AlmOrganization> {
return getJSON('/api/alm_integration/show_organization', data).then(
({ organization }) => ({
...organization,
name: organization.name || organization.key
}),
throwGlobalError
);
}

export function getRepositories(): Promise<{
almIntegration: {
installed: boolean;
Expand Down
6 changes: 4 additions & 2 deletions server/sonar-web/src/main/js/api/organizations.ts
Expand Up @@ -39,12 +39,12 @@ export function getOrganization(key: string): Promise<Organization | undefined>
}

interface GetOrganizationNavigation {
adminPages: Array<{ key: string; name: string }>;
canAdmin: boolean;
canDelete: boolean;
canProvisionProjects: boolean;
isDefault: boolean;
pages: Array<{ key: string; name: string }>;
adminPages: Array<{ key: string; name: string }>;
}

export function getOrganizationNavigation(key: string): Promise<GetOrganizationNavigation> {
Expand All @@ -54,7 +54,9 @@ export function getOrganizationNavigation(key: string): Promise<GetOrganizationN
);
}

export function createOrganization(data: OrganizationBase): Promise<Organization> {
export function createOrganization(
data: OrganizationBase & { installId?: string }
): Promise<Organization> {
return postJSON('/api/organizations/create', data).then(r => r.organization, throwGlobalError);
}

Expand Down
3 changes: 2 additions & 1 deletion server/sonar-web/src/main/js/app/components/Landing.tsx
Expand Up @@ -21,9 +21,10 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Location } from 'history';
import { CurrentUser, isLoggedIn } from '../types';
import { CurrentUser } from '../types';
import { getCurrentUser, Store } from '../../store/rootReducer';
import { getHomePageUrl } from '../../helpers/urls';
import { isLoggedIn } from '../../helpers/users';

interface StateProps {
currentUser: CurrentUser | undefined;
Expand Down
3 changes: 2 additions & 1 deletion server/sonar-web/src/main/js/app/components/StartupModal.tsx
Expand Up @@ -21,7 +21,7 @@ import * as React from 'react';
import * as PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter, WithRouterProps } from 'react-router';
import { CurrentUser, isLoggedIn } from '../types';
import { CurrentUser } from '../types';
import { differenceInDays, parseDate, toShortNotSoISOString } from '../../helpers/dates';
import { EditionKey } from '../../apps/marketplace/utils';
import { getCurrentUser, getAppState, Store } from '../../store/rootReducer';
Expand All @@ -32,6 +32,7 @@ import { save, get } from '../../helpers/storage';
import { isSonarCloud } from '../../helpers/system';
import { skipOnboarding } from '../../api/users';
import { lazyLoad } from '../../components/lazyLoad';
import { isLoggedIn } from '../../helpers/users';

const OnboardingModal = lazyLoad(() => import('../../apps/tutorials/onboarding/OnboardingModal'));
const LicensePromptModal = lazyLoad(
Expand Down
Expand Up @@ -24,7 +24,6 @@ import {
BranchLike,
Component,
CurrentUser,
isLoggedIn,
HomePageType,
HomePage,
Measure
Expand All @@ -43,6 +42,7 @@ import {
isPullRequest
} from '../../../../helpers/branches';
import { translate } from '../../../../helpers/l10n';
import { isLoggedIn } from '../../../../helpers/users';
import { getCurrentUser, Store } from '../../../../store/rootReducer';

interface StateProps {
Expand Down
Expand Up @@ -27,12 +27,13 @@ import GlobalNavUserContainer from './GlobalNavUserContainer';
import Search from '../../search/Search';
import EmbedDocsPopupHelper from '../../embed-docs-modal/EmbedDocsPopupHelper';
import * as theme from '../../../theme';
import { CurrentUser, AppState, isLoggedIn } from '../../../types';
import { CurrentUser, AppState } from '../../../types';
import NavBar from '../../../../components/nav/NavBar';
import { lazyLoad } from '../../../../components/lazyLoad';
import { getCurrentUser, getAppState, Store } from '../../../../store/rootReducer';
import { SuggestionLink } from '../../embed-docs-modal/SuggestionsProvider';
import { isSonarCloud } from '../../../../helpers/system';
import { isLoggedIn } from '../../../../helpers/users';
import './GlobalNav.css';

const GlobalNavPlus = lazyLoad(() => import('./GlobalNavPlus'), 'GlobalNavPlus');
Expand Down
Expand Up @@ -20,13 +20,14 @@
import * as React from 'react';
import * as classNames from 'classnames';
import { Link } from 'react-router';
import { isLoggedIn, CurrentUser, AppState, Extension } from '../../../types';
import { CurrentUser, AppState, Extension } from '../../../types';
import { translate } from '../../../../helpers/l10n';
import { getQualityGatesUrl, getBaseUrl } from '../../../../helpers/urls';
import { isMySet } from '../../../../apps/issues/utils';
import Dropdown from '../../../../components/controls/Dropdown';
import DropdownIcon from '../../../../components/icons-components/DropdownIcon';
import { isSonarCloud } from '../../../../helpers/system';
import { isLoggedIn } from '../../../../helpers/users';

interface Props {
appState: Pick<AppState, 'canAdmin' | 'globalPages' | 'organizationsEnabled' | 'qualifiers'>;
Expand Down
Expand Up @@ -22,12 +22,13 @@ import { Link, withRouter, WithRouterProps } from 'react-router';
import CreateFormShim from '../../../../apps/portfolio/components/CreateFormShim';
import Dropdown from '../../../../components/controls/Dropdown';
import PlusIcon from '../../../../components/icons-components/PlusIcon';
import { AppState, hasGlobalPermission, LoggedInUser } from '../../../types';
import { getPortfolioAdminUrl, getPortfolioUrl } from '../../../../helpers/urls';
import { AppState, LoggedInUser } from '../../../types';
import { getExtensionStart } from '../../extensions/utils';
import { isSonarCloud } from '../../../../helpers/system';
import { translate } from '../../../../helpers/l10n';
import { getComponentNavigation } from '../../../../api/nav';
import { translate } from '../../../../helpers/l10n';
import { isSonarCloud } from '../../../../helpers/system';
import { getPortfolioAdminUrl, getPortfolioUrl } from '../../../../helpers/urls';
import { hasGlobalPermission } from '../../../../helpers/users';

interface Props {
appState: Pick<AppState, 'qualifiers'>;
Expand Down
Expand Up @@ -22,12 +22,13 @@ import { sortBy } from 'lodash';
import * as PropTypes from 'prop-types';
import { Link } from 'react-router';
import * as theme from '../../../theme';
import { CurrentUser, LoggedInUser, isLoggedIn, Organization } from '../../../types';
import { CurrentUser, LoggedInUser, Organization } from '../../../types';
import Avatar from '../../../../components/ui/Avatar';
import OrganizationListItem from '../../../../components/ui/OrganizationListItem';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/urls';
import Dropdown from '../../../../components/controls/Dropdown';
import { isLoggedIn } from '../../../../helpers/users';

interface Props {
appState: { organizationsEnabled?: boolean };
Expand Down
Expand Up @@ -72,7 +72,7 @@
}

.menu > li > a.disabled {
color: #bbb !important;
color: var(--disableGrayText) !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
Expand Down
6 changes: 3 additions & 3 deletions server/sonar-web/src/main/js/app/styles/init/forms.css
Expand Up @@ -249,8 +249,8 @@ label[for] {
}

.radio-toggle input[type='radio']:disabled + label {
color: #bbb;
border-color: #ddd;
background: #ebebeb;
color: var(--disableGrayText);
border-color: var(--disableGrayBorder);
background: var(--disableGrayBg);
cursor: not-allowed;
}
4 changes: 2 additions & 2 deletions server/sonar-web/src/main/js/app/styles/init/icons.css
Expand Up @@ -84,12 +84,12 @@ a[class*=' icon-'] {
}

.icon-checkbox-disabled:before {
border: 1px solid #bbb;
border: 1px solid var(--disableGrayText);
cursor: not-allowed;
}

.icon-checkbox-disabled.icon-checkbox-checked:before {
background-color: #bbb;
background-color: var(--disableGrayText);
}

.icon-checkbox-invisible {
Expand Down
5 changes: 5 additions & 0 deletions server/sonar-web/src/main/js/app/styles/init/misc.css
Expand Up @@ -299,6 +299,11 @@ td.big-spacer-top {
align-items: center;
}

.display-inline-flex-baseline {
display: inline-flex !important;
align-items: baseline;
}

.display-inline-flex-center {
display: inline-flex !important;
align-items: center;
Expand Down
36 changes: 0 additions & 36 deletions server/sonar-web/src/main/js/app/styles/sonarcloud.css
Expand Up @@ -33,42 +33,6 @@
font-weight: bold;
}

.sonarcloud .flex-tabs {
display: flex;
clear: left;
margin-bottom: calc(3 * var(--gridSize));
border-bottom: 1px solid var(--barBorderColor);
font-size: var(--mediumFontSize);
}

.sonarcloud .flex-tabs > li > a {
position: relative;
display: block;
top: 1px;
height: 100%;
width: 100%;
box-sizing: border-box;
color: var(--secondFontColor);
font-weight: 600;
cursor: pointer;
padding-bottom: calc(1.5 * var(--gridSize));
border-bottom: 3px solid transparent;
transition: color 0.2s ease;
}

.sonarcloud .flex-tabs > li ~ li {
margin-left: calc(4 * var(--gridSize));
}

.sonarcloud .flex-tabs > li > a:hover {
color: var(--baseFontColor);
}

.sonarcloud .flex-tabs > li > a.selected {
color: var(--blue);
border-bottom-color: var(--blue);
}

.beta-badge {
display: inline-block;
padding: 2px 4px;
Expand Down

0 comments on commit f61d654

Please sign in to comment.