import * as React from 'react'; | import * as React from 'react'; | ||||
import tw from 'twin.macro'; | import tw from 'twin.macro'; | ||||
import { themeBorder, themeColor } from '../helpers/theme'; | import { themeBorder, themeColor } from '../helpers/theme'; | ||||
import { BasicSeparator } from './Separator'; | |||||
interface CardProps extends React.HTMLAttributes<HTMLDivElement> { | interface CardProps extends React.HTMLAttributes<HTMLDivElement> { | ||||
children: React.ReactNode; | children: React.ReactNode; | ||||
return <LightGreyCardStyled {...rest}>{children}</LightGreyCardStyled>; | return <LightGreyCardStyled {...rest}>{children}</LightGreyCardStyled>; | ||||
} | } | ||||
export function LightGreyCardTitle({ children }: Readonly<React.PropsWithChildren>) { | |||||
return ( | |||||
<> | |||||
<div className="sw-flex sw-items-center sw-justify-between sw-w-full sw-mb-4 sw-min-h-6"> | |||||
{children} | |||||
</div> | |||||
<BasicSeparator className="sw--mx-6 sw-my-0" /> | |||||
</> | |||||
); | |||||
} | |||||
export const CardWithPrimaryBackground = styled(Card)` | export const CardWithPrimaryBackground = styled(Card)` | ||||
background-color: ${themeColor('backgroundPrimary')}; | background-color: ${themeColor('backgroundPrimary')}; | ||||
`; | `; |
BasicSeparator, | BasicSeparator, | ||||
LargeCenteredLayout, | LargeCenteredLayout, | ||||
LightGreyCard, | LightGreyCard, | ||||
LightGreyCardTitle, | |||||
PageContentFontWrapper, | PageContentFontWrapper, | ||||
} from 'design-system'; | } from 'design-system'; | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { QualityGateStatus } from '../../../types/quality-gates'; | import { QualityGateStatus } from '../../../types/quality-gates'; | ||||
import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; | import { Component, MeasureEnhanced, Metric, Period, QualityGate } from '../../../types/types'; | ||||
import { AnalysisStatus } from '../components/AnalysisStatus'; | import { AnalysisStatus } from '../components/AnalysisStatus'; | ||||
import LastAnalysisLabel from '../components/LastAnalysisLabel'; | |||||
import ActivityPanel from './ActivityPanel'; | import ActivityPanel from './ActivityPanel'; | ||||
import BranchMetaTopBar from './BranchMetaTopBar'; | import BranchMetaTopBar from './BranchMetaTopBar'; | ||||
import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif'; | import FirstAnalysisNextStepsNotif from './FirstAnalysisNextStepsNotif'; | ||||
import NoCodeWarning from './NoCodeWarning'; | import NoCodeWarning from './NoCodeWarning'; | ||||
import OverallCodeMeasuresPanel from './OverallCodeMeasuresPanel'; | import OverallCodeMeasuresPanel from './OverallCodeMeasuresPanel'; | ||||
import QualityGatePanel from './QualityGatePanel'; | import QualityGatePanel from './QualityGatePanel'; | ||||
import { QualityGateStatusTitle } from './QualityGateStatusTitle'; | |||||
import SonarLintPromotion from './SonarLintPromotion'; | import SonarLintPromotion from './SonarLintPromotion'; | ||||
import { TabsPanel } from './TabsPanel'; | import { TabsPanel } from './TabsPanel'; | ||||
</> | </> | ||||
)} | )} | ||||
<AnalysisStatus className="sw-mt-6" component={component} /> | <AnalysisStatus className="sw-mt-6" component={component} /> | ||||
<div className="sw-flex sw-mt-6"> | |||||
<div className="sw-w-1/4 sw-mr-3"> | |||||
<LightGreyCard className="sw-h-max"> | |||||
<div className="sw-flex sw-gap-3 sw-mt-6"> | |||||
<div className="sw-w-1/4"> | |||||
<LightGreyCard> | |||||
<QualityGateStatusTitle /> | |||||
<QualityGatePanel | <QualityGatePanel | ||||
component={component} | component={component} | ||||
loading={loadingStatus} | loading={loadingStatus} | ||||
/> | /> | ||||
</div> | </div> | ||||
<LightGreyCard className="sw-flex-1"> | |||||
<div className="sw-flex sw-flex-col"> | |||||
<div className="sw-flex-1"> | |||||
<LightGreyCard className="sw-flex sw-flex-col"> | |||||
<LightGreyCardTitle> | |||||
<div> </div> | |||||
<LastAnalysisLabel analysisDate={branch?.analysisDate} /> | |||||
</LightGreyCardTitle> | |||||
<TabsPanel | <TabsPanel | ||||
analyses={analyses} | analyses={analyses} | ||||
appLeak={appLeak} | appLeak={appLeak} | ||||
component={component} | component={component} | ||||
loading={loadingStatus} | loading={loadingStatus} | ||||
period={period} | period={period} | ||||
branch={branch} | |||||
qgStatuses={qgStatuses} | qgStatuses={qgStatuses} | ||||
isNewCode={isNewCodeTab} | isNewCode={isNewCodeTab} | ||||
onTabSelect={selectTab} | onTabSelect={selectTab} | ||||
metrics={metrics} | metrics={metrics} | ||||
onGraphChange={onGraphChange} | onGraphChange={onGraphChange} | ||||
/> | /> | ||||
</div> | |||||
</LightGreyCard> | |||||
</LightGreyCard> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
)} | )} |
import QualityGatePanelSection from './QualityGatePanelSection'; | import QualityGatePanelSection from './QualityGatePanelSection'; | ||||
import QualityGateStatusHeader from './QualityGateStatusHeader'; | import QualityGateStatusHeader from './QualityGateStatusHeader'; | ||||
import QualityGateStatusPassedView from './QualityGateStatusPassedView'; | import QualityGateStatusPassedView from './QualityGateStatusPassedView'; | ||||
import { QualityGateStatusTitle } from './QualityGateStatusTitle'; | |||||
export interface QualityGatePanelProps { | export interface QualityGatePanelProps { | ||||
component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>; | component: Pick<Component, 'key' | 'qualifier' | 'qualityGate'>; | ||||
return ( | return ( | ||||
<div data-testid="overview__quality-gate-panel"> | <div data-testid="overview__quality-gate-panel"> | ||||
<QualityGateStatusTitle /> | |||||
<div className="sw-pt-5"> | <div className="sw-pt-5"> | ||||
<Spinner loading={loading}> | <Spinner loading={loading}> | ||||
<QualityGateStatusHeader | <QualityGateStatusHeader |
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import { BasicSeparator, HelperHintIcon, PageTitle } from 'design-system'; | |||||
import { HelperHintIcon, LightGreyCardTitle, PageTitle } from 'design-system'; | |||||
import React from 'react'; | import React from 'react'; | ||||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | import HelpTooltip from '../../../components/controls/HelpTooltip'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
export function QualityGateStatusTitle() { | export function QualityGateStatusTitle() { | ||||
return ( | return ( | ||||
<> | |||||
<div className="sw-flex sw-items-center sw-mb-4 sw--mt-2"> | |||||
<div className="sw-flex sw-items-center"> | |||||
<PageTitle as="h2" text={translate('overview.quality_gate.status')} /> | |||||
<HelpTooltip | |||||
className="sw-ml-2" | |||||
overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>} | |||||
> | |||||
<HelperHintIcon aria-label="help-tooltip" /> | |||||
</HelpTooltip> | |||||
</div> | |||||
<LightGreyCardTitle> | |||||
<div className="sw-flex sw-items-center"> | |||||
<PageTitle as="h2" text={translate('overview.quality_gate.status')} /> | |||||
<HelpTooltip | |||||
className="sw-ml-2" | |||||
overlay={<div className="sw-my-4">{translate('overview.quality_gate.help')}</div>} | |||||
> | |||||
<HelperHintIcon aria-label="help-tooltip" /> | |||||
</HelpTooltip> | |||||
</div> | </div> | ||||
<BasicSeparator className="sw--mx-6" /> | |||||
</> | |||||
</LightGreyCardTitle> | |||||
); | ); | ||||
} | } |
import styled from '@emotion/styled'; | import styled from '@emotion/styled'; | ||||
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | ||||
import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
import { Badge, BasicSeparator, LightGreyCard, TextBold, TextSubdued } from 'design-system'; | |||||
import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage, useIntl } from 'react-intl'; | import { FormattedMessage, useIntl } from 'react-intl'; | ||||
import Tooltip from '../../../components/controls/Tooltip'; | import Tooltip from '../../../components/controls/Tooltip'; | ||||
data-testid={`overview__software-impact-card-${softwareQuality}`} | data-testid={`overview__software-impact-card-${softwareQuality}`} | ||||
className="sw-w-1/3 sw-overflow-hidden sw-rounded-2 sw-p-4 sw-flex-col" | className="sw-w-1/3 sw-overflow-hidden sw-rounded-2 sw-p-4 sw-flex-col" | ||||
> | > | ||||
<div className="sw-flex sw-justify-between"> | |||||
<LightGreyCardTitle> | |||||
<TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} /> | <TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} /> | ||||
{failed && ( | {failed && ( | ||||
<Badge className="sw-h-fit" variant="deleted"> | <Badge className="sw-h-fit" variant="deleted"> | ||||
<FormattedMessage id="overview.measures.failed_badge" /> | <FormattedMessage id="overview.measures.failed_badge" /> | ||||
</Badge> | </Badge> | ||||
)} | )} | ||||
</div> | |||||
<BasicSeparator className="sw--mx-4" /> | |||||
</LightGreyCardTitle> | |||||
<div className="sw-flex sw-flex-col sw-gap-3"> | <div className="sw-flex sw-flex-col sw-gap-3"> | ||||
<div className="sw-flex sw-mt-2"> | |||||
<div className="sw-flex sw-mt-4"> | |||||
<div | <div | ||||
className={classNames('sw-flex sw-gap-1 sw-items-center', { | className={classNames('sw-flex sw-gap-1 sw-items-center', { | ||||
'sw-opacity-60': component.needIssueSync, | 'sw-opacity-60': component.needIssueSync, |
*/ | */ | ||||
import { Spinner } from '@sonarsource/echoes-react'; | import { Spinner } from '@sonarsource/echoes-react'; | ||||
import { isBefore, sub } from 'date-fns'; | import { isBefore, sub } from 'date-fns'; | ||||
import { BasicSeparator, ButtonLink, FlagMessage, LightLabel, Tabs } from 'design-system'; | |||||
import { ButtonLink, FlagMessage, LightLabel, Tabs } from 'design-system'; | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||
import DocumentationLink from '../../../components/common/DocumentationLink'; | import DocumentationLink from '../../../components/common/DocumentationLink'; | ||||
import { isDiffMetric } from '../../../helpers/measures'; | import { isDiffMetric } from '../../../helpers/measures'; | ||||
import { CodeScope } from '../../../helpers/urls'; | import { CodeScope } from '../../../helpers/urls'; | ||||
import { ApplicationPeriod } from '../../../types/application'; | import { ApplicationPeriod } from '../../../types/application'; | ||||
import { Branch } from '../../../types/branch-like'; | |||||
import { ComponentQualifier } from '../../../types/component'; | import { ComponentQualifier } from '../../../types/component'; | ||||
import { Analysis, ProjectAnalysisEventCategory } from '../../../types/project-activity'; | import { Analysis, ProjectAnalysisEventCategory } from '../../../types/project-activity'; | ||||
import { QualityGateStatus } from '../../../types/quality-gates'; | import { QualityGateStatus } from '../../../types/quality-gates'; | ||||
import { Component, Period } from '../../../types/types'; | import { Component, Period } from '../../../types/types'; | ||||
import LastAnalysisLabel from '../components/LastAnalysisLabel'; | |||||
import { MAX_ANALYSES_NB } from './ActivityPanel'; | import { MAX_ANALYSES_NB } from './ActivityPanel'; | ||||
import { LeakPeriodInfo } from './LeakPeriodInfo'; | import { LeakPeriodInfo } from './LeakPeriodInfo'; | ||||
component: Component; | component: Component; | ||||
loading?: boolean; | loading?: boolean; | ||||
period?: Period; | period?: Period; | ||||
branch?: Branch; | |||||
qgStatuses?: QualityGateStatus[]; | qgStatuses?: QualityGateStatus[]; | ||||
isNewCode: boolean; | isNewCode: boolean; | ||||
onTabSelect: (tab: CodeScope) => void; | onTabSelect: (tab: CodeScope) => void; | ||||
period, | period, | ||||
qgStatuses = [], | qgStatuses = [], | ||||
isNewCode, | isNewCode, | ||||
branch, | |||||
children, | children, | ||||
} = props; | } = props; | ||||
const isApp = component.qualifier === ComponentQualifier.Application; | const isApp = component.qualifier === ComponentQualifier.Application; | ||||
]; | ]; | ||||
return ( | return ( | ||||
<div data-testid="overview__measures-panel"> | |||||
<div className="sw-flex sw-justify-end sw-items-center sw-mb-4"> | |||||
<LastAnalysisLabel analysisDate={branch?.analysisDate} /> | |||||
</div> | |||||
<BasicSeparator className="sw--mx-6 sw-mb-3" /> | |||||
<div className="sw-mt-3" data-testid="overview__measures-panel"> | |||||
{loading ? ( | {loading ? ( | ||||
<div> | <div> | ||||
<Spinner isLoading={loading} /> | <Spinner isLoading={loading} /> |