From: Viktor Vorona Date: Fri, 23 Jun 2023 14:22:45 +0000 (+0200) Subject: SONAR-19613 Migrate About this project to MIUI X-Git-Tag: 10.2.0.77647~521 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4e7d35f61399ea04e151b5c9239982dd499957cf;p=sonarqube.git SONAR-19613 Migrate About this project to MIUI --- diff --git a/server/sonar-web/design-system/src/components/SizeIndicator.tsx b/server/sonar-web/design-system/src/components/SizeIndicator.tsx index 62cc685c2e7..a5a4d54259b 100644 --- a/server/sonar-web/design-system/src/components/SizeIndicator.tsx +++ b/server/sonar-web/design-system/src/components/SizeIndicator.tsx @@ -18,13 +18,14 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import styled from '@emotion/styled'; +import { inRange } from 'lodash'; import tw from 'twin.macro'; import { getProp, themeColor, themeContrast } from '../helpers/theme'; import { SizeLabel } from '../types/measures'; export interface Props { size?: 'xs' | 'sm' | 'md'; - value: SizeLabel; + value: number; } const SIZE_MAPPING = { @@ -34,9 +35,22 @@ const SIZE_MAPPING = { }; export function SizeIndicator({ size = 'sm', value }: Props) { + let letter: SizeLabel; + if (inRange(value, 0, 1000)) { + letter = 'XS'; + } else if (inRange(value, 1000, 10000)) { + letter = 'S'; + } else if (inRange(value, 10000, 100000)) { + letter = 'M'; + } else if (inRange(value, 100000, 500000)) { + letter = 'L'; + } else { + letter = 'XL'; + } + return ( ); } @@ -44,7 +58,7 @@ export function SizeIndicator({ size = 'sm', value }: Props) { const StyledContainer = styled.div<{ size: string }>` width: ${getProp('size')}; height: ${getProp('size')}; - font-size: ${({ size }) => (size === '2rem' ? '0.875rem' : '0.75rem')}; + font-size: ${({ size }) => (size === '2rem' ? '0.875rem' : `calc(${size}/2)`)}; color: ${themeContrast('sizeIndicator')}; background-color: ${themeColor('sizeIndicator')}; diff --git a/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx index bdd8b8c6d85..908197ec19a 100644 --- a/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/SizeIndicator-test.tsx @@ -25,14 +25,17 @@ import { FCProps } from '../../types/misc'; import { SizeLabel } from '../../types/measures'; import { SizeIndicator } from '../SizeIndicator'; -it.each(['XS', 'S', 'M', 'L', 'XL'])( - 'should display SizeIndicator with size', - (value: SizeLabel) => { - setupWithProps({ value }); - expect(screen.getByText(value)).toBeInTheDocument(); - } -); +it.each([ + [100, 'XS'], + [1100, 'S'], + [20_000, 'M'], + [200_000, 'L'], + [1_000_000, 'XL'], +])('should display SizeIndicator with size', (value: number, letter: SizeLabel) => { + setupWithProps({ value }); + expect(screen.getByText(letter)).toBeInTheDocument(); +}); function setupWithProps(props: Partial> = {}) { - return render(); + return render(); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx index 35ad1906cb9..57bd0ba3d69 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/ComponentNav-test.tsx @@ -67,7 +67,7 @@ it('renders correctly when the project binding is incorrect', () => { it('correctly returns focus to the Project Information link when the drawer is closed', () => { renderComponentNav(); screen.getByRole('link', { name: 'project.info.title' }).click(); - expect(screen.getByText('/project/info?id=my-project')).toBeInTheDocument(); + expect(screen.getByText('/project/information?id=my-project')).toBeInTheDocument(); }); function renderComponentNav(props: Partial = {}) { diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx index 93899de2a3f..dc29d25eaa2 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectCard.tsx @@ -19,6 +19,7 @@ */ import * as React from 'react'; import Link from '../../../components/common/Link'; +import MetaLink from '../../../components/common/MetaLink'; import HelpTooltip from '../../../components/controls/HelpTooltip'; import DateFromNow from '../../../components/intl/DateFromNow'; import Level from '../../../components/ui/Level'; @@ -26,7 +27,6 @@ import { translate, translateWithParameters } from '../../../helpers/l10n'; import { orderLinks } from '../../../helpers/projectLinks'; import { getProjectUrl } from '../../../helpers/urls'; import { MyProject, ProjectLink } from '../../../types/types'; -import MetaLink from '../../projectInformation/meta/MetaLink'; interface Props { project: MyProject; diff --git a/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx b/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx index 765e19b795d..223c019c707 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx @@ -28,7 +28,7 @@ import withCurrentUserContext from '../../app/components/current-user/withCurren import withMetricsContext from '../../app/components/metrics/withMetricsContext'; import { translate } from '../../helpers/l10n'; import { BranchLike } from '../../types/branch-like'; -import { ComponentQualifier } from '../../types/component'; +import { ComponentQualifier, isApplication, isProject } from '../../types/component'; import { Feature } from '../../types/features'; import { MetricKey } from '../../types/metrics'; import { Component, Dict, Measure, Metric } from '../../types/types'; @@ -82,14 +82,12 @@ export class ProjectInformationApp extends React.PureComponent { const { branchLike, component, currentUser, metrics } = this.props; const { measures } = this.state; - const canConfigureNotifications = - isLoggedIn(currentUser) && component.qualifier === ComponentQualifier.Project; + const canConfigureNotifications = isLoggedIn(currentUser) && isProject(component.qualifier); const canUseBadges = metrics !== undefined && - (component.qualifier === ComponentQualifier.Application || - component.qualifier === ComponentQualifier.Project); + (isApplication(component.qualifier) || isProject(component.qualifier)); const regulatoryReportFeatureEnabled = this.props.hasFeature(Feature.RegulatoryReport); - const isApp = component.qualifier === ComponentQualifier.Application; + const isApp = isApplication(component.qualifier); return (
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx b/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx new file mode 100644 index 00000000000..cebd49ea64c --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock'; +import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils'; +import { byRole } from '../../../helpers/testSelector'; +import routes from '../routes'; + +const componentsMock = new ComponentsServiceMock(); + +const ui = { + projectPageTitle: byRole('heading', { name: 'project.info.title' }), + applicationPageTitle: byRole('heading', { name: 'application.info.title' }), + qualityGateList: byRole('list', { name: 'project.info.quality_gate' }), + qualityProfilesList: byRole('list', { name: 'project.info.qualit_profiles' }), + link: byRole('link'), + tags: byRole('generic', { name: /tags:/ }), + size: byRole('link', { name: /project.info.see_more_info_on_x_locs/ }), + newKeyInput: byRole('textbox'), + updateInputButton: byRole('button', { name: 'update_verb' }), + resetInputButton: byRole('button', { name: 'reset_verb' }), +}; + +afterEach(() => { + componentsMock.reset(); +}); + +it('can update project key', async () => { + renderProjectInformationApp(); + expect(await ui.projectPageTitle.find()).toBeInTheDocument(); +}); + +function renderProjectInformationApp() { + return renderAppWithComponentContext( + 'project/information', + routes, + {}, + { component: componentsMock.components[0].component } + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx index 5b2b9a38d50..09dd1ec8bed 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx @@ -17,17 +17,24 @@ * 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 React from 'react'; -import PrivacyBadgeContainer from '../../../components/common/PrivacyBadgeContainer'; +import classNames from 'classnames'; +import { BasicSeparator, SubTitle } from 'design-system'; +import React, { PropsWithChildren, useContext, useEffect, useState } from 'react'; +import { getProjectLinks } from '../../../api/projectLinks'; +import { CurrentUserContext } from '../../../app/components/current-user/CurrentUserContext'; import { translate } from '../../../helpers/l10n'; -import { ComponentQualifier } from '../../../types/component'; -import { Component, Measure } from '../../../types/types'; -import MetaKey from '../meta/MetaKey'; -import MetaLinks from '../meta/MetaLinks'; -import MetaQualityGate from '../meta/MetaQualityGate'; -import MetaQualityProfiles from '../meta/MetaQualityProfiles'; -import MetaSize from '../meta/MetaSize'; -import MetaTags from '../meta/MetaTags'; +import { ComponentQualifier, Visibility } from '../../../types/component'; +import { Component, Measure, ProjectLink } from '../../../types/types'; +import { isLoggedIn } from '../../../types/users'; +import MetaDescription from './components/MetaDescription'; +import MetaHome from './components/MetaHome'; +import MetaKey from './components/MetaKey'; +import MetaLinks from './components/MetaLinks'; +import MetaQualityGate from './components/MetaQualityGate'; +import MetaQualityProfiles from './components/MetaQualityProfiles'; +import MetaSize from './components/MetaSize'; +import MetaTags from './components/MetaTags'; +import MetaVisibility from './components/MetaVisibility'; export interface AboutProjectProps { component: Component; @@ -35,73 +42,90 @@ export interface AboutProjectProps { onComponentChange: (changes: {}) => void; } -export function AboutProject(props: AboutProjectProps) { +export default function AboutProject(props: AboutProjectProps) { + const { currentUser } = useContext(CurrentUserContext); const { component, measures = [] } = props; - - const heading = React.useRef(null); const isApp = component.qualifier === ComponentQualifier.Application; + const [links, setLinks] = useState(undefined); - React.useEffect(() => { - if (heading.current) { - // a11y: provide focus to the heading when the Project Information is opened. - heading.current.focus(); + useEffect(() => { + if (!isApp) { + getProjectLinks(component.key).then( + (links) => setLinks(links), + () => {} + ); } - }, [heading]); + }, [component.key, isApp]); return ( <>
-

- {translate(isApp ? 'application' : 'project', 'info.title')} -

+ {translate(isApp ? 'application' : 'project', 'about.title')}
-
-
-
-

{translate('project.info.description')}

- {component.visibility && ( - 0)) && ( + + {component.qualityGate && } + + {component.qualityProfiles && component.qualityProfiles.length > 0 && ( + )} -
+ + )} - {component.description && ( -

{component.description}

- )} + + + - -
+ {component.visibility === Visibility.Private && ( + + + + )} -
- -
+ + + - {!isApp && - (component.qualityGate || - (component.qualityProfiles && component.qualityProfiles.length > 0)) && ( -
- {component.qualityGate && } + + + - {component.qualityProfiles && component.qualityProfiles.length > 0 && ( - - )} -
- )} + + + - {!isApp && } + {!isApp && links && links.length > 0 && ( + + + + )} -
- -
-
+ {isLoggedIn(currentUser) && ( + + + + )} ); } -export default AboutProject; +interface ProjectInformationSectionProps { + last?: boolean; + className?: string; +} + +function ProjectInformationSection(props: PropsWithChildren) { + const { children, className, last = false } = props; + return ( + <> +
{children}
+ {!last && } + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx new file mode 100644 index 00000000000..9a9e3e5e4a8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { TextMuted } from 'design-system'; +import * as React from 'react'; +import { translate } from '../../../../helpers/l10n'; + +interface Props { + description?: string; + isApp?: boolean; +} + +export default function MetaDescription({ description, isApp }: Props) { + return ( + <> +

{translate('project.info.description')}

+ {description ? ( +

{description}

+ ) : ( + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaHome.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaHome.tsx new file mode 100644 index 00000000000..cf79ab1f0b5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaHome.tsx @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { Checkbox, HelperHintIcon } from 'design-system'; +import React, { useContext } from 'react'; +import { setHomePage } from '../../../../api/users'; +import { CurrentUserContext } from '../../../../app/components/current-user/CurrentUserContext'; +import HelpTooltip from '../../../../components/controls/HelpTooltip'; +import { DEFAULT_HOMEPAGE } from '../../../../components/controls/HomePageSelect'; +import { translate } from '../../../../helpers/l10n'; +import { isSameHomePage } from '../../../../helpers/users'; +import { HomePage, LoggedInUser, isLoggedIn } from '../../../../types/users'; + +export interface MetaHomeProps { + componentKey: string; + currentUser: LoggedInUser; + isApp?: boolean; +} + +export default function MetaHome({ componentKey, currentUser, isApp }: MetaHomeProps) { + const { updateCurrentUserHomepage } = useContext(CurrentUserContext); + const currentPage: HomePage = { + component: componentKey, + type: 'PROJECT', + branch: undefined, + }; + + const setCurrentUserHomepage = async (homepage: HomePage) => { + if (isLoggedIn(currentUser)) { + await setHomePage(homepage); + + updateCurrentUserHomepage(homepage); + } + }; + + const handleClick = (value: boolean) => { + setCurrentUserHomepage(value ? currentPage : DEFAULT_HOMEPAGE); + }; + + return ( + <> +
+

{translate('project.info.make_home.title')}

+ + {translate(isApp ? 'application' : 'project', 'info.make_home.tooltip')} +

+ } + > + +
+
+ + + {translate(isApp ? 'application' : 'project', 'info.make_home.label')} + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx new file mode 100644 index 00000000000..f5b023c180a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaKey.tsx @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { ClipboardIconButton, CodeSnippet, HelperHintIcon } from 'design-system'; +import * as React from 'react'; +import HelpTooltip from '../../../../components/controls/HelpTooltip'; +import { translate } from '../../../../helpers/l10n'; + +interface MetaKeyProps { + componentKey: string; + qualifier: string; +} + +export default function MetaKey({ componentKey, qualifier }: MetaKeyProps) { + return ( + <> +
+

{translate('overview.project_key', qualifier)}

+ + {translate('overview.project_key.tooltip', qualifier)} +

+ } + > + +
+
+
+
+ + +
+
+ + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx new file mode 100644 index 00000000000..15cc747d867 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx @@ -0,0 +1,43 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 React from 'react'; +import MetaLink from '../../../../components/common/MetaLink'; +import { translate } from '../../../../helpers/l10n'; +import { orderLinks } from '../../../../helpers/projectLinks'; +import { ProjectLink } from '../../../../types/types'; + +interface Props { + links: ProjectLink[]; +} + +export default function MetaLinks({ links }: Props) { + const orderedLinks = orderLinks(links); + + return ( + <> +

{translate('overview.external_links')}

+
    + {orderedLinks.map((link) => ( + + ))} +
+ + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx new file mode 100644 index 00000000000..745aa85062f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityGate.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { Link } from 'design-system'; +import * as React from 'react'; +import { translate } from '../../../../helpers/l10n'; +import { getQualityGateUrl } from '../../../../helpers/urls'; + +interface Props { + qualityGate: { isDefault?: boolean; name: string }; +} + +export default function MetaQualityGate({ qualityGate }: Props) { + return ( + <> +

{translate('project.info.quality_gate')}

+ +
    +
  • + {qualityGate.isDefault && ( + ({translate('default')}) + )} + {qualityGate.name} +
  • +
+ + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx new file mode 100644 index 00000000000..87d7b0488e9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaQualityProfiles.tsx @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { Badge, Link } from 'design-system'; +import React, { useContext, useEffect } from 'react'; +import { searchRules } from '../../../../api/rules'; +import { LanguagesContext } from '../../../../app/components/languages/LanguagesContext'; +import Tooltip from '../../../../components/controls/Tooltip'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import { getQualityProfileUrl } from '../../../../helpers/urls'; +import { Languages } from '../../../../types/languages'; +import { ComponentQualityProfile, Dict } from '../../../../types/types'; + +interface Props { + headerClassName?: string; + profiles: ComponentQualityProfile[]; +} + +export function MetaQualityProfiles({ headerClassName, profiles }: Props) { + const [deprecatedByKey, setDeprecatedByKey] = React.useState>({}); + const languages = useContext(LanguagesContext); + + useEffect(() => { + const existingProfiles = profiles.filter((p) => !p.deleted); + const requests = existingProfiles.map((profile) => { + const data = { + activation: 'true', + ps: 1, + qprofile: profile.key, + statuses: 'DEPRECATED', + }; + return searchRules(data).then((r) => r.paging.total); + }); + Promise.all(requests).then( + (responses) => { + const deprecatedByKey: Dict = {}; + responses.forEach((count, i) => { + const profileKey = existingProfiles[i].key; + deprecatedByKey[profileKey] = count; + }); + setDeprecatedByKey(deprecatedByKey); + }, + () => {} + ); + }, [profiles]); + + return ( + <> +

+ {translate('overview.quality_profiles')} +

+
    + {profiles.map((profile) => ( + + ))} +
+ + ); +} + +function ProfileItem({ + profile, + languages, + deprecatedByKey, +}: { + profile: ComponentQualityProfile; + languages: Languages; + deprecatedByKey: Dict; +}) { + const languageFromStore = languages[profile.language]; + const languageName = languageFromStore ? languageFromStore.name : profile.language; + const count = deprecatedByKey[profile.key] || 0; + + return ( +
  • +
    + {languageName} +
    + {profile.deleted ? ( + +
    {profile.name}
    +
    + ) : ( + <> + + + {profile.name} + + + {count > 0 && ( + + + + {translate('deprecated')} + + + + )} + + )} +
    +
    +
  • + ); +} + +export default MetaQualityProfiles; diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx new file mode 100644 index 00000000000..a015f048d94 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaSize.tsx @@ -0,0 +1,91 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { DrilldownLink, SizeIndicator } from 'design-system'; +import * as React from 'react'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import { formatMeasure, localizeMetric } from '../../../../helpers/measures'; +import { getComponentDrilldownUrl } from '../../../../helpers/urls'; +import { ComponentQualifier } from '../../../../types/component'; +import { MetricKey } from '../../../../types/metrics'; +import { Component, Measure } from '../../../../types/types'; + +interface MetaSizeProps { + component: Component; + measures: Measure[]; +} + +export default function MetaSize({ component, measures }: MetaSizeProps) { + const isApp = component.qualifier === ComponentQualifier.Application; + const ncloc = measures.find((measure) => measure.metric === MetricKey.ncloc); + const projects = isApp + ? measures.find((measure) => measure.metric === MetricKey.projects) + : undefined; + const url = getComponentDrilldownUrl({ + componentKey: component.key, + metric: MetricKey.ncloc, + listView: true, + }); + + return ( + <> +
    +

    {localizeMetric(MetricKey.ncloc)}

    + ({translate('project.info.main_branch')}) +
    +
    + {ncloc && ncloc.value ? ( + <> + + + {formatMeasure(ncloc.value, 'SHORT_INT')} + + + + + + + + ) : ( + 0 + )} + + {isApp && ( + + {projects ? ( + + {formatMeasure(projects.value, 'SHORT_INT')} + + ) : ( + 0 + )} + + {translate('metric.projects.name')} + + + )} +
    + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx new file mode 100644 index 00000000000..68abe4393c5 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx @@ -0,0 +1,131 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { Tags, TagsSelector } from 'design-system'; +import { difference, without } from 'lodash'; +import React, { useState } from 'react'; +import { searchProjectTags, setApplicationTags, setProjectTags } from '../../../../api/components'; +import Tooltip from '../../../../components/controls/Tooltip'; +import { PopupPlacement } from '../../../../components/ui/popups'; +import { translate } from '../../../../helpers/l10n'; +import { ComponentQualifier } from '../../../../types/component'; +import { Component } from '../../../../types/types'; + +interface Props { + component: Component; + onComponentChange: (changes: {}) => void; +} + +export default function MetaTags(props: Props) { + const [open, setOpen] = React.useState(false); + + const canUpdateTags = () => { + const { configuration } = props.component; + return configuration && configuration.showSettings; + }; + + const setTags = (values: string[]) => { + const { component } = props; + + if (component.qualifier === ComponentQualifier.Application) { + return setApplicationTags({ + application: component.key, + tags: values.join(','), + }); + } + + return setProjectTags({ + project: component.key, + tags: values.join(','), + }); + }; + + const handleSetProjectTags = (values: string[]) => { + setTags(values).then( + () => props.onComponentChange({ tags: values }), + () => {} + ); + }; + + const tags = props.component.tags || []; + + return ( + <> +

    {translate('tags')}

    + } + popupPlacement={PopupPlacement.Bottom} + tags={tags} + tagsToDisplay={2} + tooltip={Tooltip} + open={open} + onClose={() => setOpen(false)} + /> + + ); +} + +interface MetaTagsSelectorProps { + selectedTags: string[]; + setProjectTags: (tags: string[]) => void; +} + +const LIST_SIZE = 10; + +function MetaTagsSelector({ selectedTags, setProjectTags }: MetaTagsSelectorProps) { + const [searchResult, setSearchResult] = useState([]); + const availableTags = difference(searchResult, selectedTags); + + const onSearch = (query: string) => { + return searchProjectTags({ + q: query, + ps: Math.min(selectedTags.length - 1 + LIST_SIZE, 100), + }).then( + ({ tags }) => setSearchResult(tags), + () => {} + ); + }; + + const onSelect = (tag: string) => { + setProjectTags([...selectedTags, tag]); + }; + + const onUnselect = (tag: string) => { + setProjectTags(without(selectedTags, tag)); + }; + + return ( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx new file mode 100644 index 00000000000..c5fdb3d6b27 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaVisibility.tsx @@ -0,0 +1,37 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 * as React from 'react'; +import PrivacyBadgeContainer from '../../../../components/common/PrivacyBadgeContainer'; +import { translate } from '../../../../helpers/l10n'; +import { Visibility } from '../../../../types/component'; + +interface Props { + qualifier: string; + visibility: Visibility; +} + +export default function MetaVisibility({ qualifier, visibility }: Props) { + return ( + <> +

    {translate('visibility')}

    + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaKey-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaKey-test.tsx new file mode 100644 index 00000000000..17d2a8c315d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaKey-test.tsx @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react'; +import * as React from 'react'; +import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; +import { ComponentQualifier } from '../../../../../types/component'; +import MetaKey from '../MetaKey'; + +it('should render correctly', () => { + renderMetaKey(); + expect( + screen.getByText(`overview.project_key.${ComponentQualifier.Project}`) + ).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Copy to clipboard' })).toBeInTheDocument(); +}); + +function renderMetaKey(props: Partial[0]> = {}) { + return renderComponent( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx new file mode 100644 index 00000000000..8d2f51f585b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaQualityProfiles-test.tsx @@ -0,0 +1,84 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react'; +import * as React from 'react'; +import { searchRules } from '../../../../../api/rules'; +import { LanguagesContext } from '../../../../../app/components/languages/LanguagesContext'; +import { mockLanguage, mockPaging, mockQualityProfile } from '../../../../../helpers/testMocks'; +import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; +import { SearchRulesResponse } from '../../../../../types/coding-rules'; +import { Dict } from '../../../../../types/types'; +import { MetaQualityProfiles } from '../MetaQualityProfiles'; + +jest.mock('../../../../../api/rules', () => { + return { + searchRules: jest.fn().mockResolvedValue({ + total: 10, + }), + }; +}); + +it('should render correctly', async () => { + const totals: Dict = { + js: 0, + ts: 10, + css: 0, + }; + jest + .mocked(searchRules) + .mockImplementation(({ qprofile }: { qprofile: string }): Promise => { + return Promise.resolve({ + rules: [], + paging: mockPaging({ + total: totals[qprofile], + }), + }); + }); + + renderMetaQualityprofiles(); + + expect(await screen.findByText('overview.deleted_profile.javascript')).toBeInTheDocument(); + expect(screen.getByText('overview.deprecated_profile.10')).toBeInTheDocument(); +}); + +function renderMetaQualityprofiles( + overrides: Partial[0]> = {} +) { + return renderComponent( + + + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaTags-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaTags-test.tsx new file mode 100644 index 00000000000..88511e5e193 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaTags-test.tsx @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { act, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import * as React from 'react'; +import { setApplicationTags, setProjectTags } from '../../../../../api/components'; +import { mockComponent } from '../../../../../helpers/mocks/component'; +import { renderComponent } from '../../../../../helpers/testReactTestingUtils'; +import { ComponentQualifier } from '../../../../../types/component'; +import MetaTags from '../MetaTags'; + +jest.mock('../../../../../api/components', () => ({ + setApplicationTags: jest.fn().mockResolvedValue(true), + setProjectTags: jest.fn().mockResolvedValue(true), + searchProjectTags: jest.fn().mockResolvedValue({ tags: ['best', 'useless'] }), +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +it('should render without tags and admin rights', async () => { + renderMetaTags(); + + expect(await screen.findByText('no_tags')).toBeInTheDocument(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); +}); + +it('should allow to edit tags for a project', async () => { + const user = userEvent.setup(); + + const onComponentChange = jest.fn(); + const component = mockComponent({ + key: 'my-second-project', + tags: ['foo', 'bar'], + configuration: { + showSettings: true, + }, + name: 'MySecondProject', + }); + + renderMetaTags({ component, onComponentChange }); + + expect(await screen.findByText('foo, bar')).toBeInTheDocument(); + expect(screen.getByRole('button')).toBeInTheDocument(); + + await act(() => user.click(screen.getByRole('button', { name: 'foo, bar +' }))); + + expect(await screen.findByRole('checkbox', { name: 'best' })).toBeInTheDocument(); + + await user.click(screen.getByRole('checkbox', { name: 'best' })); + expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo', 'bar', 'best'] }); + + onComponentChange.mockClear(); + + /* + * Since we're not actually updating the tags, we're back to having the foo, bar only + */ + await user.click(screen.getByRole('checkbox', { name: 'bar' })); + expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo'] }); + + expect(setProjectTags).toHaveBeenCalled(); + expect(setApplicationTags).not.toHaveBeenCalled(); +}); + +it('should set tags for an app', async () => { + const user = userEvent.setup(); + + renderMetaTags({ + component: mockComponent({ + configuration: { + showSettings: true, + }, + qualifier: ComponentQualifier.Application, + }), + }); + + await act(() => user.click(screen.getByRole('button', { name: 'no_tags +' }))); + + await user.click(await screen.findByRole('checkbox', { name: 'best' })); + + expect(setProjectTags).not.toHaveBeenCalled(); + expect(setApplicationTags).toHaveBeenCalled(); +}); + +function renderMetaTags(overrides: Partial[0]> = {}) { + const component = mockComponent({ + configuration: { + showSettings: false, + }, + }); + + return renderComponent( + + ); +} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaKey.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaKey.tsx deleted file mode 100644 index 8532fac8291..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaKey.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import { ClipboardButton } from '../../../components/controls/clipboard'; -import { translate } from '../../../helpers/l10n'; - -export interface MetaKeyProps { - componentKey: string; - qualifier: string; -} - -export default function MetaKey({ componentKey, qualifier }: MetaKeyProps) { - return ( - <> -

    {translate('overview.project_key', qualifier)}

    -
    - - -
    - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLink.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLink.tsx deleted file mode 100644 index e81f53c7554..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLink.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import isValidUri from '../../../app/utils/isValidUri'; -import { ClearButton } from '../../../components/controls/buttons'; -import ProjectLinkIcon from '../../../components/icons/ProjectLinkIcon'; -import { getLinkName } from '../../../helpers/projectLinks'; -import { ProjectLink } from '../../../types/types'; - -interface Props { - iconOnly?: boolean; - link: ProjectLink; -} - -interface State { - expanded: boolean; -} - -export default class MetaLink extends React.PureComponent { - state = { expanded: false }; - - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - this.setState(({ expanded }) => ({ expanded: !expanded })); - }; - - handleCollapse = () => { - this.setState({ expanded: false }); - }; - - handleSelect = (event: React.MouseEvent) => { - event.currentTarget.select(); - }; - - render() { - const { iconOnly, link } = this.props; - const linkTitle = getLinkName(link); - const isValid = isValidUri(link.url); - return ( -
  • - - - {!iconOnly && linkTitle} - - {this.state.expanded && ( -
    - - -
    - )} -
  • - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLinks.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLinks.tsx deleted file mode 100644 index c6df449cb8f..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaLinks.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import { getProjectLinks } from '../../../api/projectLinks'; -import { translate } from '../../../helpers/l10n'; -import { orderLinks } from '../../../helpers/projectLinks'; -import { LightComponent, ProjectLink } from '../../../types/types'; -import MetaLink from './MetaLink'; - -interface Props { - component: LightComponent; -} - -interface State { - links?: ProjectLink[]; -} - -export default class MetaLinks extends React.PureComponent { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.loadLinks(); - } - - componentDidUpdate(prevProps: Props) { - if (prevProps.component.key !== this.props.component.key) { - this.loadLinks(); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - loadLinks = () => - getProjectLinks(this.props.component.key).then( - (links) => { - if (this.mounted) { - this.setState({ links }); - } - }, - () => {} - ); - - render() { - const { links } = this.state; - - if (!links || links.length === 0) { - return null; - } - - const orderedLinks = orderLinks(links); - - return ( -
    -

    {translate('overview.external_links')}

    -
      - {orderedLinks.map((link) => ( - - ))} -
    -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityGate.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityGate.tsx deleted file mode 100644 index 94719fc965e..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityGate.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import Link from '../../../components/common/Link'; -import { translate } from '../../../helpers/l10n'; -import { getQualityGateUrl } from '../../../helpers/urls'; - -interface Props { - qualityGate: { isDefault?: boolean; name: string }; -} - -export default function MetaQualityGate({ qualityGate }: Props) { - return ( - <> -

    {translate('project.info.quality_gate')}

    - -
      -
    • - {qualityGate.isDefault && ( - ({translate('default')}) - )} - {qualityGate.name} -
    • -
    - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityProfiles.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityProfiles.tsx deleted file mode 100644 index 1788efdad17..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaQualityProfiles.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import { searchRules } from '../../../api/rules'; -import withLanguagesContext from '../../../app/components/languages/withLanguagesContext'; -import Link from '../../../components/common/Link'; -import Tooltip from '../../../components/controls/Tooltip'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { getQualityProfileUrl } from '../../../helpers/urls'; -import { Languages } from '../../../types/languages'; -import { ComponentQualityProfile, Dict } from '../../../types/types'; - -interface Props { - headerClassName?: string; - languages: Languages; - profiles: ComponentQualityProfile[]; -} - -interface State { - deprecatedByKey: Dict; -} - -export class MetaQualityProfiles extends React.PureComponent { - mounted = false; - state: State = { deprecatedByKey: {} }; - - componentDidMount() { - this.mounted = true; - this.loadDeprecatedRules(); - } - - componentWillUnmount() { - this.mounted = false; - } - - loadDeprecatedRules() { - const existingProfiles = this.props.profiles.filter((p) => !p.deleted); - const requests = existingProfiles.map((profile) => - this.loadDeprecatedRulesForProfile(profile.key) - ); - Promise.all(requests).then( - (responses) => { - if (this.mounted) { - const deprecatedByKey: Dict = {}; - responses.forEach((count, i) => { - const profileKey = existingProfiles[i].key; - deprecatedByKey[profileKey] = count; - }); - this.setState({ deprecatedByKey }); - } - }, - () => {} - ); - } - - loadDeprecatedRulesForProfile(profileKey: string) { - const data = { - activation: 'true', - ps: 1, - qprofile: profileKey, - statuses: 'DEPRECATED', - }; - return searchRules(data).then((r) => r.paging.total); - } - - getDeprecatedRulesCount(profile: { key: string }) { - const count = this.state.deprecatedByKey[profile.key]; - return count || 0; - } - - renderProfile(profile: ComponentQualityProfile) { - const languageFromStore = this.props.languages[profile.language]; - const languageName = languageFromStore ? languageFromStore.name : profile.language; - - const inner = ( -
    - ({languageName}) - {profile.deleted ? ( - profile.name - ) : ( - - - {profile.name} - - - )} -
    - ); - - if (profile.deleted) { - const tooltip = translateWithParameters('overview.deleted_profile', profile.name); - return ( - -
  • {inner}
  • -
    - ); - } - - const count = this.getDeprecatedRulesCount(profile); - - if (count > 0) { - const tooltip = translateWithParameters('overview.deprecated_profile', count); - return ( - -
  • {inner}
  • -
    - ); - } - - return
  • {inner}
  • ; - } - - render() { - const { headerClassName, profiles } = this.props; - - return ( - <> -

    {translate('overview.quality_profiles')}

    - -
      - {profiles.map((profile) => this.renderProfile(profile))} -
    - - ); - } -} - -export default withLanguagesContext(MetaQualityProfiles); diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaSize.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaSize.tsx deleted file mode 100644 index 499d3b18492..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaSize.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import DrilldownLink from '../../../components/shared/DrilldownLink'; -import SizeRating from '../../../components/ui/SizeRating'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { formatMeasure, localizeMetric } from '../../../helpers/measures'; -import { ComponentQualifier } from '../../../types/component'; -import { MetricKey } from '../../../types/metrics'; -import { Component, Measure } from '../../../types/types'; - -export interface MetaSizeProps { - component: Component; - measures: Measure[]; -} - -export default function MetaSize({ component, measures }: MetaSizeProps) { - const isApp = component.qualifier === ComponentQualifier.Application; - const ncloc = measures.find((measure) => measure.metric === MetricKey.ncloc); - const projects = isApp - ? measures.find((measure) => measure.metric === MetricKey.projects) - : undefined; - - return ( - <> -
    -

    {localizeMetric(MetricKey.ncloc)}

    - ({translate('project.info.main_branch')}) -
    -
    - {ncloc && ncloc.value ? ( - <> - - - {formatMeasure(ncloc.value, 'SHORT_INT')} - - - - - - - - ) : ( - 0 - )} - - {isApp && ( - - {projects ? ( - - {formatMeasure(projects.value, 'SHORT_INT')} - - ) : ( - 0 - )} - - {translate('metric.projects.name')} - - - )} -
    - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTags.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTags.tsx deleted file mode 100644 index d12a0bf75ce..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTags.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 * as React from 'react'; -import { setApplicationTags, setProjectTags } from '../../../api/components'; -import Dropdown from '../../../components/controls/Dropdown'; -import { ButtonLink } from '../../../components/controls/buttons'; -import TagsList from '../../../components/tags/TagsList'; -import { PopupPlacement } from '../../../components/ui/popups'; -import { translate } from '../../../helpers/l10n'; -import { ComponentQualifier } from '../../../types/component'; -import { Component } from '../../../types/types'; -import MetaTagsSelector from './MetaTagsSelector'; - -interface Props { - component: Component; - onComponentChange: (changes: {}) => void; -} - -export default class MetaTags extends React.PureComponent { - canUpdateTags = () => { - const { configuration } = this.props.component; - return configuration && configuration.showSettings; - }; - - setTags = (values: string[]) => { - const { component } = this.props; - - if (component.qualifier === ComponentQualifier.Application) { - return setApplicationTags({ - application: component.key, - tags: values.join(','), - }); - } - - return setProjectTags({ - project: component.key, - tags: values.join(','), - }); - }; - - handleSetProjectTags = (values: string[]) => { - this.setTags(values).then( - () => this.props.onComponentChange({ tags: values }), - () => {} - ); - }; - - render() { - const tags = this.props.component.tags || []; - - return this.canUpdateTags() ? ( -
    - - } - overlayPlacement={PopupPlacement.BottomLeft} - > - - - - -
    - ) : ( -
    - -
    - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTagsSelector.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTagsSelector.tsx deleted file mode 100644 index d2602d40f2d..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/MetaTagsSelector.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 { difference, without } from 'lodash'; -import * as React from 'react'; -import { searchProjectTags } from '../../../api/components'; -import TagsSelector from '../../../components/tags/TagsSelector'; - -interface Props { - selectedTags: string[]; - setProjectTags: (tags: string[]) => void; -} - -interface State { - searchResult: string[]; -} - -const LIST_SIZE = 10; - -export default class MetaTagsSelector extends React.PureComponent { - mounted = false; - state: State = { searchResult: [] }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - onSearch = (query: string) => { - return searchProjectTags({ - q: query, - ps: Math.min(this.props.selectedTags.length - 1 + LIST_SIZE, 100), - }).then( - ({ tags }) => { - if (this.mounted) { - this.setState({ searchResult: tags }); - } - }, - () => {} - ); - }; - - onSelect = (tag: string) => { - this.props.setProjectTags([...this.props.selectedTags, tag]); - }; - - onUnselect = (tag: string) => { - this.props.setProjectTags(without(this.props.selectedTags, tag)); - }; - - render() { - const availableTags = difference(this.state.searchResult, this.props.selectedTags); - return ( - - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaKey-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaKey-test.tsx deleted file mode 100644 index 0fd2885b200..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaKey-test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { ComponentQualifier } from '../../../../types/component'; -import MetaKey, { MetaKeyProps } from '../MetaKey'; - -it('should render correctly', () => { - renderMetaKey(); - expect( - screen.getByLabelText(`overview.project_key.${ComponentQualifier.Project}`) - ).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'overview.project_key.click_to_copy' }) - ).toBeInTheDocument(); -}); - -function renderMetaKey(props: Partial = {}) { - return renderComponent( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaQualityProfiles-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaQualityProfiles-test.tsx deleted file mode 100644 index 842045daff7..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaQualityProfiles-test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react'; -import * as React from 'react'; -import { searchRules } from '../../../../api/rules'; -import { mockLanguage, mockPaging, mockQualityProfile } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { SearchRulesResponse } from '../../../../types/coding-rules'; -import { Dict } from '../../../../types/types'; -import { MetaQualityProfiles } from '../MetaQualityProfiles'; - -jest.mock('../../../../api/rules', () => { - return { - searchRules: jest.fn().mockResolvedValue({ - total: 10, - }), - }; -}); - -it('should render correctly', async () => { - const totals: Dict = { - js: 0, - ts: 10, - css: 0, - }; - jest - .mocked(searchRules) - .mockImplementation(({ qprofile }: { qprofile: string }): Promise => { - return Promise.resolve({ - rules: [], - paging: mockPaging({ - total: totals[qprofile], - }), - }); - }); - - renderMetaQualityprofiles(); - - expect(await screen.findByText('overview.deleted_profile.javascript')).toBeInTheDocument(); - expect(screen.getByText('overview.deprecated_profile.10')).toBeInTheDocument(); -}); - -function renderMetaQualityprofiles(overrides: Partial = {}) { - return renderComponent( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaTags-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaTags-test.tsx deleted file mode 100644 index 04b073db83e..00000000000 --- a/server/sonar-web/src/main/js/apps/projectInformation/meta/__tests__/MetaTags-test.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { searchProjectTags, setApplicationTags, setProjectTags } from '../../../../api/components'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import { ComponentQualifier } from '../../../../types/component'; -import MetaTags from '../MetaTags'; - -jest.mock('../../../../api/components', () => ({ - setApplicationTags: jest.fn().mockResolvedValue(true), - setProjectTags: jest.fn().mockResolvedValue(true), - searchProjectTags: jest.fn(), -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should render without tags and admin rights', async () => { - renderMetaTags(); - - expect(await screen.findByText('no_tags')).toBeInTheDocument(); - expect(screen.queryByRole('button')).not.toBeInTheDocument(); -}); - -it('should allow to edit tags for a project', async () => { - const user = userEvent.setup(); - jest.mocked(searchProjectTags).mockResolvedValue({ tags: ['best', 'useless'] }); - - const onComponentChange = jest.fn(); - const component = mockComponent({ - key: 'my-second-project', - tags: ['foo', 'bar'], - configuration: { - showSettings: true, - }, - name: 'MySecondProject', - }); - - renderMetaTags({ component, onComponentChange }); - - expect(await screen.findByText('foo, bar')).toBeInTheDocument(); - expect(screen.getByRole('button')).toBeInTheDocument(); - - await user.click(screen.getByRole('button', { name: 'tags_list_x.foo, bar' })); - - expect(await screen.findByText('best')).toBeInTheDocument(); - - await user.click(screen.getByText('best')); - expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo', 'bar', 'best'] }); - - onComponentChange.mockClear(); - - /* - * Since we're not actually updating the tags, we're back to having the foo, bar only - */ - await user.click(screen.getByText('bar')); - expect(onComponentChange).toHaveBeenCalledWith({ tags: ['foo'] }); - - expect(setProjectTags).toHaveBeenCalled(); - expect(setApplicationTags).not.toHaveBeenCalled(); -}); - -it('should set tags for an app', async () => { - const user = userEvent.setup(); - - renderMetaTags({ - component: mockComponent({ - configuration: { - showSettings: true, - }, - qualifier: ComponentQualifier.Application, - }), - }); - - await user.click(screen.getByRole('button', { name: 'tags_list_x.no_tags' })); - - await user.click(screen.getByText('best')); - - expect(setProjectTags).not.toHaveBeenCalled(); - expect(setApplicationTags).toHaveBeenCalled(); -}); - -function renderMetaTags(overrides: Partial = {}) { - const component = mockComponent({ - configuration: { - showSettings: false, - }, - }); - - return renderComponent( - - ); -} diff --git a/server/sonar-web/src/main/js/components/common/MetaLink.tsx b/server/sonar-web/src/main/js/components/common/MetaLink.tsx new file mode 100644 index 00000000000..c2d73bd9323 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/MetaLink.tsx @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { CloseIcon, InputField, InteractiveIcon, Link } from 'design-system'; +import React, { useState } from 'react'; +import isValidUri from '../../app/utils/isValidUri'; +import { translate } from '../../helpers/l10n'; +import { getLinkName } from '../../helpers/projectLinks'; +import { ProjectLink } from '../../types/types'; +import { ClearButton } from '../controls/buttons'; +import ProjectLinkIcon from '../icons/ProjectLinkIcon'; + +interface Props { + iconOnly?: boolean; + link: ProjectLink; + // TODO Remove this prop when all links are migrated to the new design + miui?: boolean; +} + +export default function MetaLink({ iconOnly, link, miui }: Props) { + const [expanded, setExpanded] = useState(false); + + const handleClick = (event: React.MouseEvent) => { + event.preventDefault(); + setExpanded((expanded) => !expanded); + }; + + const handleCollapse = () => { + setExpanded(false); + }; + + const handleSelect = (event: React.MouseEvent) => { + event.currentTarget.select(); + }; + + const linkTitle = getLinkName(link); + const isValid = isValidUri(link.url); + return miui ? ( +
  • + } + > + {!iconOnly && linkTitle} + + + {expanded && ( +
    + + +
    + )} +
  • + ) : ( +
  • + + + {!iconOnly && linkTitle} + + {expanded && ( +
    + + +
    + )} +
  • + ); +} diff --git a/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx b/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx index 86606a750d0..9392d6400e9 100644 --- a/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx @@ -17,6 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { LinkExternalIcon } from '@primer/octicons-react'; +import { HomeIcon } from 'design-system'; import * as React from 'react'; import BugTrackerIcon from './BugTrackerIcon'; import ContinuousIntegrationIcon from './ContinuousIntegrationIcon'; @@ -27,19 +29,30 @@ import SCMIcon from './SCMIcon'; interface ProjectLinkIconProps { type: string; + miui?: boolean; } -export default function ProjectLinkIcon({ type, ...iconProps }: IconProps & ProjectLinkIconProps) { - switch (type) { - case 'issue': - return ; - case 'homepage': - return ; - case 'ci': - return ; - case 'scm': - return ; - default: - return ; - } +export default function ProjectLinkIcon({ + miui, + type, + ...iconProps +}: IconProps & ProjectLinkIconProps) { + const getIcon = (): any => { + switch (type) { + case 'issue': + return BugTrackerIcon; + case 'homepage': + return miui ? HomeIcon : HouseIcon; + case 'ci': + return ContinuousIntegrationIcon; + case 'scm': + return SCMIcon; + default: + return miui ? LinkExternalIcon : DetachIcon; + } + }; + + const Icon = getIcon(); + + return ; } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 57699322713..7fd7a3df05c 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -1825,12 +1825,23 @@ projects_management.filter_by_visibility=Filter by visibility project.info.title=Project Information application.info.title=Application Information +project.about.title=About this Project +application.about.title=About this Application project.info.description=Description +project.info.empty_description=No description added for this project. +application.info.empty_description=No description added for this application. project.info.quality_gate=Quality Gate used project.info.to_notifications=Set notifications project.info.notifications=Notifications project.info.main_branch=Main branch project.info.see_more_info_on_x_locs=See more information on your {0} lines of code +project.info.make_home.title=Use as homepage +project.info.make_home.label=Make this project my homepage +application.info.make_home.label=Make this application my homepage +project.info.make_home.tooltip=This means you'll be redirected to this project whenever you log in to sonarqube or click on the top-left SonarQube logo. +application.info.make_home.tooltip=This means you'll be redirected to this application whenever you log in to sonarqube or click on the top-left SonarQube logo. +overview.project_key.tooltip.TRK=Your project key is a unique identifier for your project. If you are using Maven, make sure the key matches the "groupId:artifactId" format. +overview.project_key.tooltip.APP=Your application key is a unique identifier for your application. #------------------------------------------------------------------------------ #