diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-10-24 10:36:44 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-11-16 20:21:04 +0100 |
commit | 7c133fcc9d877837e18fef5c9d83cce463adbd7f (patch) | |
tree | f0e71b401bb7e9348e26d1576c7f9285b0ea6d3a /server/sonar-web | |
parent | f714aa7a66fd87a39898390edd0c15f0c77dd248 (diff) | |
download | sonarqube-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.tsx | 2 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx | 4 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx | 31 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx | 21 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/helpers/__tests__/strings-test.ts | 37 | ||||
-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 +} |