aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-10-24 10:36:44 +0200
committerSonarTech <sonartech@sonarsource.com>2018-11-16 20:21:04 +0100
commit7c133fcc9d877837e18fef5c9d83cce463adbd7f (patch)
treef0e71b401bb7e9348e26d1576c7f9285b0ea6d3a /server/sonar-web
parentf714aa7a66fd87a39898390edd0c15f0c77dd248 (diff)
downloadsonarqube-7c133fcc9d877837e18fef5c9d83cce463adbd7f.tar.gz
sonarqube-7c133fcc9d877837e18fef5c9d83cce463adbd7f.zip
SONAR-11321 Suggest valid organization key
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx31
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx21
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/strings-test.ts37
-rw-r--r--server/sonar-web/src/main/js/helpers/strings.ts (renamed from server/sonar-web/src/main/js/helpers/latinize.ts)25
6 files changed, 104 insertions, 16 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
index 76723ce03cb..d36fa16ea74 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CustomRuleFormModal.tsx
@@ -23,12 +23,12 @@ import Modal from '../../../components/controls/Modal';
import { translate } from '../../../helpers/l10n';
import MarkdownTips from '../../../components/common/MarkdownTips';
import { SEVERITIES, RULE_TYPES, RULE_STATUSES } from '../../../helpers/constants';
-import latinize from '../../../helpers/latinize';
import Select from '../../../components/controls/Select';
import TypeHelper from '../../../components/shared/TypeHelper';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { createRule, updateRule } from '../../../api/rules';
import { csvEscape } from '../../../helpers/csv';
+import { latinize } from '../../../helpers/strings';
import { SubmitButton, ResetButtonLink } from '../../../components/ui/buttons';
import { Alert } from '../../../components/ui/Alert';
diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
index fc2b246d428..92de554862d 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx
@@ -23,6 +23,7 @@ import AutoOrganizationBind from './AutoOrganizationBind';
import ChooseRemoteOrganizationStep from './ChooseRemoteOrganizationStep';
import OrganizationDetailsForm from './OrganizationDetailsForm';
import OrganizationDetailsStep from './OrganizationDetailsStep';
+import RadioToggle from '../../../components/controls/RadioToggle';
import {
AlmApplication,
AlmOrganization,
@@ -31,9 +32,8 @@ import {
} from '../../../app/types';
import { bindAlmOrganization } from '../../../api/alm-integration';
import { sanitizeAlmId } from '../../../helpers/almIntegrations';
-import { getBaseUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';
-import RadioToggle from '../../../components/controls/RadioToggle';
+import { getBaseUrl } from '../../../helpers/urls';
export enum Filters {
Bind = 'bind',
diff --git a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
index 81b219784cd..1023408535c 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import { times } from 'lodash';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { Helmet } from 'react-helmet';
@@ -48,6 +49,7 @@ import {
} from '../../../app/types';
import { hasAdvancedALMIntegration, isPersonal } from '../../../helpers/almIntegrations';
import { translate } from '../../../helpers/l10n';
+import { slugify } from '../../../helpers/strings';
import { getOrganizationUrl } from '../../../helpers/urls';
import * as api from '../../../api/organizations';
import * as actions from '../../../store/organizations';
@@ -117,20 +119,37 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
});
};
+ fetchValidOrgKey = (almOrganization: AlmOrganization) => {
+ const key = slugify(almOrganization.key);
+ const keys = [key, ...times(9, i => `${key}-${i + 1}`)];
+ return api
+ .getOrganizations({ organizations: keys.join(',') })
+ .then(
+ ({ organizations }) => {
+ const availableKey = keys.find(key => !organizations.find(o => o.key === key));
+ return availableKey || `${key}-${Math.ceil(Math.random() * 1000) + 10}`;
+ },
+ () => key
+ )
+ .then(key => {
+ return { ...almOrganization, key };
+ });
+ };
+
fetchAlmOrganization = (installationId: string) => {
this.setState({ almOrgLoading: true });
- return getAlmOrganization({ installationId }).then(
- almOrganization => {
+ return getAlmOrganization({ installationId })
+ .then(this.fetchValidOrgKey)
+ .then(almOrganization => {
if (this.mounted) {
this.setState({ almOrganization, almOrgLoading: false });
}
- },
- () => {
+ })
+ .catch(() => {
if (this.mounted) {
this.setState({ almOrgLoading: false });
}
- }
- );
+ });
};
fetchSubscriptionPlans = () => {
diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
index eb9fce91998..aa8b0b6c155 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
@@ -51,6 +51,10 @@ jest.mock('../../../../api/alm-integration', () => ({
})
}));
+jest.mock('../../../../api/organizations', () => ({
+ getOrganization: jest.fn().mockResolvedValue(undefined)
+}));
+
const user: LoggedInUser = {
groups: [],
isLoggedIn: true,
@@ -106,6 +110,23 @@ it('should render with auto personal organization bind page', async () => {
expect(wrapper).toMatchSnapshot();
});
+it('should slugify and find a uniq organization key', async () => {
+ (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({
+ key: 'Foo&Bar',
+ name: 'Foo & Bar',
+ avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4',
+ type: 'USER'
+ });
+ const wrapper = shallowRender({
+ currentUser: { ...user, externalProvider: 'github' },
+ location: { query: { installation_id: 'foo' } } as Location // eslint-disable-line camelcase
+ });
+ await waitAndUpdate(wrapper);
+ expect(wrapper.find('AutoOrganizationCreate').prop('almOrganization')).toMatchObject({
+ key: 'foo-and-bar'
+ });
+});
+
it('should switch tabs', async () => {
const replace = jest.fn();
const wrapper = shallowRender({
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/strings-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/strings-test.ts
new file mode 100644
index 00000000000..db2a5eafd75
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/__tests__/strings-test.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { latinize, slugify } from '../strings';
+
+describe('#latinize', () => {
+ it('should remove diacritics and replace them with normal letters', () => {
+ expect(latinize('âêîôûŵŷäëïöüẅÿàèìòùẁỳáéíóúẃýøāēīūčģķļņšž')).toBe(
+ 'aeiouwyaeiouwyaeiouwyaeiouwyoaeiucgklnsz'
+ );
+ expect(latinize('ASDFGhjklQWERTz')).toBe('ASDFGhjklQWERTz');
+ });
+});
+
+describe('#slugify', () => {
+ it('should transform text into a slug', () => {
+ expect(slugify('Luke Sky&Walker')).toBe('luke-sky-and-walker');
+ expect(slugify('tèst_:-ng><@')).toBe('test-ng');
+ expect(slugify('my-valid-slug-1')).toBe('my-valid-slug-1');
+ });
+});
diff --git a/server/sonar-web/src/main/js/helpers/latinize.ts b/server/sonar-web/src/main/js/helpers/strings.ts
index 7d5cefe98f7..ac40fd2e466 100644
--- a/server/sonar-web/src/main/js/helpers/latinize.ts
+++ b/server/sonar-web/src/main/js/helpers/strings.ts
@@ -394,15 +394,26 @@ const defaultDiacriticsRemovalap = [
];
const diacriticsMap: { [x: string]: string } = {};
-for (let i = 0; i < defaultDiacriticsRemovalap.length; i++) {
- const letters = defaultDiacriticsRemovalap[i].letters.split('');
- for (let j = 0; j < letters.length; j++) {
- diacriticsMap[letters[j]] = defaultDiacriticsRemovalap[i].base;
- }
-}
+defaultDiacriticsRemovalap.forEach(defaultDiacritic =>
+ defaultDiacritic.letters.split('').forEach(letter => {
+ diacriticsMap[letter] = defaultDiacritic.base;
+ })
+);
// "what?" version ... http://jsperf.com/diacritics/12
-export default function removeDiacritics(str: string): string {
+export function latinize(str: string): string {
// eslint-disable-next-line no-control-regex
return str.replace(/[^\u0000-\u007E]/g, a => diacriticsMap[a] || a);
}
+
+// Inspired from https://github.com/SonarSource/sonar-enterprise/blob/master/sonar-core/src/main/java/org/sonar/core/util/Slug.java
+export function slugify(text: string) {
+ return latinize(text.trim().toLowerCase())
+ .replace(/&/g, '-and-') // Replace & with 'and'
+ .replace(/[^\w-]+/g, '-') // Replace all non-word chars with dash
+ .replace(/\s+/g, '-') // Replace whitespaces with dash
+ .replace(/[·/_,:;]/g, '-') // Replace special chars with dash
+ .replace(/--+/g, '-') // Replace multiple dash with single dash
+ .replace(/^-+/, '') // Remove heading dash
+ .replace(/-+$/, ''); // Remove trailing dash
+}