diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2024-01-24 16:45:15 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-01-29 20:03:17 +0000 |
commit | a2a48d174c84e8bdaa3867dc6283c0b1abdf1d42 (patch) | |
tree | 20129305648d14b4d3780c4a667db85d1597e9de | |
parent | 2828d81c75068bec023ec8dc006e7500c659208c (diff) | |
download | sonarqube-a2a48d174c84e8bdaa3867dc6283c0b1abdf1d42.tar.gz sonarqube-a2a48d174c84e8bdaa3867dc6283c0b1abdf1d42.zip |
SONAR-21482 My Projects adopts the new UI
8 files changed, 111 insertions, 288 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index dc38bafbca8..55900e06cb4 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -87,6 +87,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [ '/admin/extension/license/support', '/admin/audit', '/admin/projects_management', + '/account/projects', ]; export default function GlobalContainer() { diff --git a/server/sonar-web/src/main/js/apps/account/account.css b/server/sonar-web/src/main/js/apps/account/account.css index 7138c62f5e2..0578071bf8f 100644 --- a/server/sonar-web/src/main/js/apps/account/account.css +++ b/server/sonar-web/src/main/js/apps/account/account.css @@ -63,168 +63,6 @@ border-top: 1px solid var(--barBorderColor); } -.account-projects-list > li { - padding: 15px 20px; -} - -.account-projects-list > li + li { - border-top: 1px solid var(--barBorderColor); -} - -.account-project-side { - float: right; - margin-left: 10px; - text-align: right; -} - -.account-project-analysis { - line-height: var(--controlHeight); - color: var(--secondFontColor); - font-size: var(--smallFontSize); -} - -.account-project-card { - position: relative; - display: block; -} - -.account-project-name { - display: inline-block; - vertical-align: top; - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.account-project-name > a { - border-bottom-color: #d0d0d0; - color: var(--baseFontColor); -} - -.account-project-name > a:hover { - border-bottom-color: var(--lightBlue); - color: var(--blue); -} - -.account-project-quality-gate { - display: inline-block; - vertical-align: top; - line-height: var(--controlHeight); - margin-left: 8px; -} - -.account-project-description { - margin-top: 6px; - line-height: 1.5; -} - -.account-project-links { - margin-top: 6px; -} - -.account-project-key { - margin-top: 6px; - color: var(--secondFontColor); - font-size: var(--smallFontSize); -} - -.my-activity-issues { - position: relative; - display: flex; - justify-content: center; - margin-bottom: 70px; -} - -.my-activity-issues:after { - position: absolute; - z-index: 5; - top: -15px; - left: 50%; - width: 1px; - height: 100px; - background-color: var(--barBorderColor); - transform: rotate(30deg); - content: ''; -} - -.my-activity-issues > a { - display: block; - padding: 15px 20px; - border: none; - border-radius: 2px; - color: var(--baseFontColor); -} - -.my-activity-issues > a:hover { - background-color: var(--barBackgroundColor); -} - -.my-activity-recent-issues { - margin-right: 50px; - text-align: right; -} - -.my-activity-recent-issues .my-activity-issues-note { - text-align: left; -} - -.my-activity-all-issues { - margin-left: 50px; -} - -.my-activity-issues-number { - display: inline-block; - vertical-align: middle; - line-height: 36px; - font-size: 36px; - font-weight: 300; -} - -.my-activity-issues-note { - display: inline-block; - vertical-align: middle; - padding-left: 10px; - padding-top: 2px; - line-height: 16px; - font-size: var(--smallFontSize); -} - -.my-activity-projects { - width: 360px; - margin: 0 auto; - padding: 40px 0; -} - -.my-activity-projects-header { - line-height: var(--controlHeight); - margin-bottom: 15px; - padding: 0 10px; -} - -.my-activity-projects > ul > li + li { - border-top: 1px solid var(--barBorderColor); -} - -.my-activity-projects > ul > li > a { - display: block; - padding: 15px 10px; - border: none; -} - -.my-activity-projects > ul > li > a:hover { - background-color: var(--barBackgroundColor); -} - -.my-activity-projects .level { - width: 60px; -} - -.my-activity-projects .more { - margin-top: 30px; - text-align: center; -} - .notifications-table { margin-top: calc(-2 * var(--gridSize)); } 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 833425e19a6..ecb186bf184 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 @@ -17,22 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { + Card, + DiscreetLink, + LightPrimary, + Note, + QualityGateIndicator, + SubHeading, + UnorderedList, +} from 'design-system'; import * as React from 'react'; -import Link from '../../../components/common/Link'; import MetaLink from '../../../components/common/MetaLink'; -import HelpTooltip from '../../../components/controls/HelpTooltip'; +import Tooltip from '../../../components/controls/Tooltip'; import DateFromNow from '../../../components/intl/DateFromNow'; -import Level from '../../../components/ui/Level'; import { translate, translateWithParameters } from '../../../helpers/l10n'; +import { formatMeasure } from '../../../helpers/measures'; import { orderLinks } from '../../../helpers/projectLinks'; import { getProjectUrl } from '../../../helpers/urls'; -import { MyProject, ProjectLink } from '../../../types/types'; +import { MetricType } from '../../../types/metrics'; +import { MyProject, ProjectLink, Status } from '../../../types/types'; interface Props { project: MyProject; } -export default function ProjectCard({ project }: Props) { +export default function ProjectCard({ project }: Readonly<Props>) { const { links } = project; const orderedLinks: ProjectLink[] = orderLinks( @@ -49,53 +58,54 @@ export default function ProjectCard({ project }: Props) { const { lastAnalysisDate } = project; + const formatted = formatMeasure(project.qualityGate, MetricType.Level); + const qualityGateLabel = translateWithParameters('overview.quality_gate_x', formatted); + return ( - <div className="account-project-card clearfix"> - <aside className="account-project-side"> + <Card> + <aside className="sw-float-right sw-flex sw-flex-col sw-items-end sw-gap-2"> {lastAnalysisDate !== undefined ? ( - <div className="account-project-analysis"> + <Note> <DateFromNow date={lastAnalysisDate}> {(fromNow) => translateWithParameters('my_account.projects.analyzed_x', fromNow)} </DateFromNow> - </div> + </Note> ) : ( - <div className="account-project-analysis"> - {translate('my_account.projects.never_analyzed')} - </div> + <Note>{translate('my_account.projects.never_analyzed')}</Note> )} {project.qualityGate !== undefined && ( - <div className="account-project-quality-gate"> - {project.qualityGate === 'WARN' && ( - <HelpTooltip - className="little-spacer-right" - overlay={translate('quality_gates.conditions.warning.tooltip')} - /> - )} - <Level aria-label={translate('quality_gates.status')} level={project.qualityGate} /> + <div> + <Tooltip overlay={qualityGateLabel}> + <span className="sw-flex sw-items-center"> + <QualityGateIndicator + status={(project.qualityGate as Status) ?? 'NONE'} + ariaLabel={qualityGateLabel} + /> + <LightPrimary className="sw-ml-2 sw-body-sm-highlight">{formatted}</LightPrimary> + </span> + </Tooltip> </div> )} </aside> - <h3 className="account-project-name"> - <Link to={getProjectUrl(project.key)}>{project.name}</Link> - </h3> + <SubHeading as="h3"> + <DiscreetLink to={getProjectUrl(project.key)}>{project.name}</DiscreetLink> + </SubHeading> + + <Note>{project.key}</Note> + + {!!project.description && <div className="sw-mt-2">{project.description}</div>} {orderedLinks.length > 0 && ( - <div className="account-project-links"> - <ul className="list-inline"> + <div className="sw-mt-2"> + <UnorderedList className="sw-flex sw-gap-4"> {orderedLinks.map((link) => ( - <MetaLink iconOnly key={link.id} link={link} /> + <MetaLink key={link.id} link={link} /> ))} - </ul> + </UnorderedList> </div> )} - - <div className="account-project-key">{project.key}</div> - - {!!project.description && ( - <div className="account-project-description">{project.description}</div> - )} - </div> + </Card> ); } diff --git a/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx b/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx index 45a717c2901..08c88a3cd67 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/Projects.tsx @@ -30,35 +30,36 @@ interface Props { total?: number; } -export default function Projects(props: Props) { +export default function Projects(props: Readonly<Props>) { const { projects } = props; return ( <div id="account-projects"> - {projects.length === 0 ? ( - <div className="js-no-results">{translate('my_account.projects.no_results')}</div> - ) : ( - <p>{translate('my_account.projects.description')}</p> - )} + <div className="sw-mt-8"> + {projects.length === 0 + ? translate('my_account.projects.no_results') + : translate('my_account.projects.description')} + </div> {projects.length > 0 && ( - <ul className="account-projects-list"> - {projects.map((project) => ( - <li key={project.key}> - <ProjectCard project={project} /> - </li> - ))} - </ul> - )} + <> + <ul className="sw-mt-4 sw-flex sw-flex-col sw-gap-4"> + {projects.map((project) => ( + <li key={project.key}> + <ProjectCard project={project} /> + </li> + ))} + </ul> - {projects.length > 0 && ( - <ListFooter - count={projects.length} - loadMore={props.loadMore} - loading={props.loading} - ready={!props.loading} - total={props.total || 0} - /> + <ListFooter + count={projects.length} + loadMore={props.loadMore} + loading={props.loading} + ready={!props.loading} + total={props.total ?? 0} + useMIUIButtons + /> + </> )} </div> ); diff --git a/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx b/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx index dcc7cb1ef10..e8b863d0274 100644 --- a/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx +++ b/server/sonar-web/src/main/js/apps/account/projects/ProjectsContainer.tsx @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { LargeCenteredLayout, PageContentFontWrapper, Spinner } from 'design-system'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; import { getMyProjects } from '../../../api/components'; @@ -62,27 +63,22 @@ export default class ProjectsContainer extends React.PureComponent<{}, State> { }; render() { - const helmet = <Helmet title={translate('my_account.projects')} />; - - if (this.state.projects == null) { - return ( - <div className="text-center"> - {helmet} - <i className="spinner spacer" /> - </div> - ); - } + const { loading, projects = [], total } = this.state; return ( - <div className="account-body account-container"> - {helmet} - <Projects - loadMore={this.loadMore} - loading={this.state.loading} - projects={this.state.projects} - total={this.state.total} - /> - </div> + <LargeCenteredLayout as="main"> + <PageContentFontWrapper className="sw-body-sm sw-py-8"> + <Helmet title={translate('my_account.projects')} /> + <Spinner loading={loading && projects.length === 0}> + <Projects + loadMore={this.loadMore} + loading={loading} + projects={projects} + total={total} + /> + </Spinner> + </PageContentFontWrapper> + </LargeCenteredLayout> ); } } 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 index 27aa46835d3..8891c5311cc 100644 --- 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 @@ -27,7 +27,7 @@ interface Props { links: ProjectLink[]; } -export default function MetaLinks({ links }: Props) { +export default function MetaLinks({ links }: Readonly<Props>) { const orderedLinks = orderLinks(links); return ( @@ -35,7 +35,7 @@ export default function MetaLinks({ links }: Props) { <h3 id="external-links">{translate('overview.external_links')}</h3> <ul className="project-info-list" aria-labelledby="external-links"> {orderedLinks.map((link) => ( - <MetaLink miui key={link.id} link={link} /> + <MetaLink key={link.id} link={link} /> ))} </ul> </> diff --git a/server/sonar-web/src/main/js/components/common/MetaLink.tsx b/server/sonar-web/src/main/js/components/common/MetaLink.tsx index 26dd69e2e07..b4df193db66 100644 --- a/server/sonar-web/src/main/js/components/common/MetaLink.tsx +++ b/server/sonar-web/src/main/js/components/common/MetaLink.tsx @@ -17,23 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import styled from '@emotion/styled'; 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) { +export default function MetaLink({ iconOnly, link }: Readonly<Props>) { const [expanded, setExpanded] = useState(false); const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => { @@ -51,29 +49,20 @@ export default function MetaLink({ iconOnly, link, miui }: Props) { const linkTitle = getLinkName(link); const isValid = isValidUri(link.url); - return miui ? ( + return ( <li> - <Link - isExternal + <StyledLink to={link.url} preventDefault={!isValid} onClick={isValid ? undefined : handleClick} - rel="nofollow noreferrer noopener" - target="_blank" - icon={<ProjectLinkIcon miui className="little-spacer-right" type={link.type} />} + icon={<ProjectLinkIcon miui type={link.type} />} > {!iconOnly && linkTitle} - </Link> + </StyledLink> {expanded && ( - <div className="little-spacer-top display-flex-center"> - <InputField - className="overview-key width-80" - onClick={handleSelect} - readOnly - type="text" - value={link.url} - /> + <div className="sw-mt-1 sw-flex sw-items-center"> + <InputField onClick={handleSelect} readOnly type="text" value={link.url} size="large" /> <InteractiveIcon Icon={CloseIcon} aria-label={translate('hide')} @@ -83,31 +72,21 @@ export default function MetaLink({ iconOnly, link, miui }: Props) { </div> )} </li> - ) : ( - <li> - <a - className="link-no-underline" - href={isValid ? link.url : undefined} - onClick={isValid ? undefined : handleClick} - rel="nofollow noreferrer noopener" - target="_blank" - title={linkTitle} - > - <ProjectLinkIcon className="little-spacer-right" type={link.type} /> - {!iconOnly && linkTitle} - </a> - {expanded && ( - <div className="little-spacer-top display-flex-center"> - <input - className="overview-key width-80" - onClick={handleSelect} - readOnly - type="text" - value={link.url} - /> - <ClearButton className="little-spacer-left" onClick={handleCollapse} /> - </div> - )} - </li> ); } + +/* + * Override the spacing to make it smaller + * 1rem = 16px for the icon width + * + + * 0.5 rem = margin '2' + */ +const StyledLink = styled(Link)` + margin-left: 1.5rem; + + & > svg, + & > img { + margin-right: 0.5rem; + margin-left: -1.5rem; + } +`; 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 406b403c26a..80754c1d075 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2252,8 +2252,6 @@ quality_gates.conditions.overall_code.description=Conditions on overall code app quality_gates.conditions.overall_code_1=1 condition failed on overall code quality_gates.conditions.overall_code_x={0} conditions failed on overall code quality_gates.conditions.operator=Operator -quality_gates.conditions.warning=Warning -quality_gates.conditions.warning.tooltip=Warning status is deprecated and will disappear with the next update of the Quality Gate. quality_gates.conditions.value=Value quality_gates.conditions.where=Where? quality_gates.duplicated_conditions=This quality gate has duplicated conditions: @@ -2678,8 +2676,8 @@ my_account.tokens_last_usage=Last use my_account.tokens.expiration=Expiration my_account.tokens.expired=Token is expired my_account.projects=Projects -my_account.projects.description=Those projects are the ones you are administering. -my_account.projects.no_results=You are not administering any project yet. +my_account.projects.description=You have admin privileges over the following projects. +my_account.projects.no_results=You have no project admin privileges. my_account.projects.analyzed_x=Analyzed {0} my_account.projects.never_analyzed=Never analyzed my_account.search_project=Search Project |