feat: [code-1195]: add enhance ments for ci integration in left pane (#931)

This commit is contained in:
Calvin Lee 2023-12-22 02:21:23 +00:00 committed by Harness
parent c0bca6eea1
commit 7118dd6f83
5 changed files with 239 additions and 52 deletions

View File

@ -42,7 +42,8 @@ interface ExecutionStatusProps {
}
export enum ExecutionStateExtended {
FAILED = 'failed'
FAILED = 'failed',
ABORTED = 'aborted'
}
export const ExecutionStatus: React.FC<ExecutionStatusProps> = ({
@ -96,12 +97,16 @@ export const ExecutionStatus: React.FC<ExecutionStatusProps> = ({
icon: 'execution-stopped',
css: null,
title: getString('killed').toLocaleUpperCase()
},
[ExecutionStateExtended.ABORTED]: {
icon: 'execution-stopped',
css: null,
title: getString('killed').toLocaleUpperCase()
}
}),
[getString, inExecution, isCi]
)
const map = useMemo(() => maps[status], [maps, status])
return (
<Text
tag="span"

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 32 32"><path fill="url(#a)" fill-rule="evenodd" d="M29 16a13.02 13.02 0 0 1-.854 4.6.387.387 0 0 1-.637.13l-3.99-3.94a.534.534 0 0 1-.085-.628c.432-.827.649-1.773.649-2.762.056-3.222-2.6-5.878-5.879-5.878-1.04 0-1.994.26-2.823.706a.53.53 0 0 1-.623-.08l-3.551-3.504a.48.48 0 0 1 .154-.794A12.79 12.79 0 0 1 16 3c7.178 0 13 5.822 13 13M8.476 5.42a.582.582 0 0 1 .746.072l4.036 4.035c.178.178.193.46.053.67a5.829 5.829 0 0 0-.985 3.26 5.934 5.934 0 0 0 5.935 5.934 5.829 5.829 0 0 0 3.26-.985.531.531 0 0 1 .67.054l4.367 4.368a.582.582 0 0 1 .065.756C24.225 26.86 20.4 29 16 29 8.822 29 3 23.178 3 16c0-4.403 2.144-8.231 5.476-10.58Zm9.785 11.71a3.932 3.932 0 0 0 3.956-3.956 3.932 3.932 0 0 0-3.956-3.957 3.932 3.932 0 0 0-3.957 3.957 3.932 3.932 0 0 0 3.957 3.956" clip-rule="evenodd"/><defs><linearGradient id="a" x1="-5.398" x2="11.398" y1="11.398" y2="37.398" gradientUnits="userSpaceOnUse"><stop stop-color="#73DFE7"/><stop offset="1" stop-color="#0095F7"/></linearGradient></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -38,6 +38,36 @@
width: 100%;
height: 100%;
}
.leftPaneContent {
align-items: center !important;
display: flex;
flex-direction: column;
background: var(--primary-bg);
.leftPaneMenuItem {
width: 350px;
border-radius: 8px;
box-shadow: 0px 0.5px 2px 0px rgba(96, 97, 112, 0.16), 0px 0px 1px 0px rgba(40, 41, 61, 0.08);
background: var(--grey-0);
&.expanded {
.chevron {
transform: rotate(90deg) !important;
}
.menuItem {
border-bottom: unset !important;
> .layout {
padding: 0px var(--spacing-xxxlarge);
border-bottom: unset !important;
> .uid {
color: var(--grey-600) !important;
font-weight: 500 !important;
font-size: 13px !important;
}
}
}
}
}
}
.menu {
.menuItem {
@ -302,3 +332,21 @@
}
}
}
.hideStages {
> .menuItem {
display: none;
}
}
:global {
.bp3-popover-wrapper {
> .bp3-popover-target {
> .uid {
color: var(--grey-600) !important;
font-weight: 500 !important;
font-size: 13px !important;
}
}
}
}

View File

@ -25,8 +25,11 @@ export declare const forPipeline: string
export declare const header: string
export declare const headerLayout: string
export declare const hidden: string
export declare const hideStages: string
export declare const invert: string
export declare const layout: string
export declare const leftPaneContent: string
export declare const leftPaneMenuItem: string
export declare const logViewer: string
export declare const main: string
export declare const markdown: string

View File

@ -22,10 +22,16 @@ import cx from 'classnames'
import { useHistory } from 'react-router-dom'
import { Container, Layout, Text, FlexExpander, Utils } from '@harnessio/uicore'
import { Color, FontVariation } from '@harnessio/design-system'
import { ButtonRoleProps, PullRequestCheckType, PullRequestSection, timeDistance } from 'utils/Utils'
import {
ButtonRoleProps,
PullRequestCheckType,
PullRequestSection,
generateAlphaNumericHash,
timeDistance
} from 'utils/Utils'
import { useAppContext } from 'AppContext'
import { useQueryParams } from 'hooks/useQueryParams'
import type { TypesCheck, TypesStage } from 'services/code'
import type { EnumCheckPayloadKind, TypesCheck, TypesStage } from 'services/code'
import { ExecutionState, ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import { CheckPipelineStages } from './CheckPipelineStages'
import { ChecksProps, findDefaultExecution } from './ChecksUtils'
@ -36,6 +42,13 @@ interface ChecksMenuProps extends ChecksProps {
setSelectedStage: (stage: TypesStage | null) => void
}
type TypesCheckPayloadExtended = EnumCheckPayloadKind | 'harness_stage'
type ExpandedStates = { [key: string]: boolean }
type ElapsedTimeStatusMap = { [key: string]: { status: 'string'; time: string } }
enum CheckKindPayload {
HARNESS_STAGE = 'harness_stage'
}
export const ChecksMenu: React.FC<ChecksMenuProps> = ({
repoMetadata,
pullRequestMetadata,
@ -43,13 +56,12 @@ export const ChecksMenu: React.FC<ChecksMenuProps> = ({
onDataItemChanged,
setSelectedStage: setSelectedStageFromProps
}) => {
const { routes } = useAppContext()
const { routes, standalone } = useAppContext()
const history = useHistory()
const { uid } = useQueryParams<{ uid: string }>()
const [selectedUID, setSelectedUID] = React.useState<string | undefined>()
const [selectedStage, setSelectedStage] = useState<TypesStage | null>(null)
const checksData = useMemo(() => sortBy(prChecksDecisionResult?.data || [], ['uid']), [prChecksDecisionResult?.data])
useMemo(() => {
if (selectedUID) {
const selectedDataItem = checksData.find(item => item.uid === selectedUID)
@ -90,38 +102,151 @@ export const ChecksMenu: React.FC<ChecksMenuProps> = ({
onDataItemChanged,
selectedStage
])
const [expandedStates, setExpandedStates] = useState<{ [key: string]: boolean }>({})
const [statusTimeStates, setStatusTimeStates] = useState<{ [key: string]: { status: string; time: string } }>({})
const groupByPipeline = (data: TypesCheck[]) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return data.reduce((acc: any, item: any) => {
const hash = generateAlphaNumericHash(6)
const pipelineId =
(item?.payload?.kind as TypesCheckPayloadExtended) === CheckKindPayload.HARNESS_STAGE
? item?.payload?.data?.pipeline_identifier
: `raw-${hash}`
if (!acc[pipelineId]) {
acc[pipelineId] = []
}
acc[pipelineId].push(item)
return acc
}, {})
}
const toggleExpandedState = (key: string) => {
setExpandedStates(prevStates => ({
...prevStates,
[key]: !prevStates[key]
}))
}
const groupedData = useMemo(() => groupByPipeline(checksData), [checksData])
useEffect(() => {
const initialStates: ExpandedStates = {}
const initialMap: ElapsedTimeStatusMap = {}
Object.keys(groupedData).forEach(key => {
const findStatus = () => {
const statusPriority = ['running', 'failure', 'error', 'success']
for (const status of statusPriority) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const foundObject = groupedData[key].find((obj: any) => obj.status === status)
if (foundObject) return foundObject
}
return null
}
const dataArr = groupedData[key]
if (groupedData && dataArr) {
let startTime = 0
let endTime = 0
dataArr.map((item: TypesCheck) => {
if (item) {
startTime += item.created ? item?.created : 0
endTime += item.updated ? item?.updated : 0
}
})
const res = findStatus()
initialMap[key] = { status: res.status, time: timeDistance(startTime, endTime) }
}
if (uid) {
initialStates[key] = uid.includes(key) ? true : false // or true if you want them initially expanded
} else {
initialStates[key] = false
}
})
setStatusTimeStates(initialMap)
setExpandedStates(initialStates)
}, [groupedData, uid])
return (
<Container className={css.menu}>
{checksData.map(itemData => (
<CheckMenuItem
repoMetadata={repoMetadata}
pullRequestMetadata={pullRequestMetadata}
prChecksDecisionResult={prChecksDecisionResult}
key={itemData.uid}
itemData={itemData}
isPipeline={itemData.payload?.kind === PullRequestCheckType.PIPELINE}
isSelected={itemData.uid === selectedUID}
onClick={stage => {
setSelectedUID(itemData.uid)
setSelectedStage(stage || null)
setSelectedStageFromProps(stage || null)
<Layout.Vertical padding={{ top: 'large' }} spacing={'small'} className={cx(css.menu, css.leftPaneContent)}>
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
{Object.entries(groupedData).map(([pipelineId, checks]: any) => (
<Container
key={pipelineId}
onClick={() => {
toggleExpandedState(pipelineId)
}}
className={cx(css.leftPaneMenuItem, {
[css.expanded]: expandedStates[pipelineId] && !pipelineId.includes('raw-') && !standalone,
[css.layout]: !pipelineId.includes('raw-') && !standalone,
[css.menuItem]: !pipelineId.includes('raw-') && !standalone,
[css.hideStages]: !expandedStates[pipelineId] && !pipelineId.includes('raw-') && !standalone
})}>
{!standalone && !pipelineId.includes('raw-') && (
<Layout.Horizontal className={css.layout}>
<Render when={statusTimeStates[pipelineId]?.status}>
<ExecutionStatus
className={cx(css.status, css.noShrink)}
status={statusTimeStates[pipelineId]?.status as ExecutionState}
iconSize={16}
noBackground
iconOnly
/>
</Render>
<Text className={css.uid} lineClamp={1} padding={{ left: 'small' }}>
{pipelineId}
</Text>
<FlexExpander />
<Render when={statusTimeStates[pipelineId]?.time}>
<Text
color={Color.GREY_300}
padding={{ right: 'small' }}
font={{ variation: FontVariation.SMALL }}
className={css.noShrink}>
{statusTimeStates[pipelineId]?.time}
</Text>
</Render>
<NavArrowRight
color={Utils.getRealCSSColor(Color.GREY_500)}
className={cx(css.noShrink, css.chevron)}
strokeWidth="1.5"
/>
</Layout.Horizontal>
)}
{(checks as TypesCheck[]).map((itemData: TypesCheck) => (
<CheckMenuItem
repoMetadata={repoMetadata}
pullRequestMetadata={pullRequestMetadata}
prChecksDecisionResult={prChecksDecisionResult}
key={itemData.uid}
itemData={itemData}
isPipeline={itemData.payload?.kind === PullRequestCheckType.PIPELINE}
isSelected={itemData.uid === selectedUID}
onClick={stage => {
setSelectedUID(itemData.uid)
setSelectedStage(stage || null)
setSelectedStageFromProps(stage || null)
history.replace(
routes.toCODEPullRequest({
repoPath: repoMetadata.path as string,
pullRequestId: String(pullRequestMetadata.number),
pullRequestSection: PullRequestSection.CHECKS
}) + `?uid=${itemData.uid}${stage ? `&stageId=${stage.name}` : ''}`
)
}}
setSelectedStage={stage => {
setSelectedStage(stage)
setSelectedStageFromProps(stage)
}}
/>
history.replace(
routes.toCODEPullRequest({
repoPath: repoMetadata.path as string,
pullRequestId: String(pullRequestMetadata.number),
pullRequestSection: PullRequestSection.CHECKS
}) + `?uid=${itemData.uid}${stage ? `&stageId=${stage.name}` : ''}`
)
}}
setSelectedStage={stage => {
setSelectedStage(stage)
setSelectedStageFromProps(stage)
}}
/>
))}
</Container>
))}
</Container>
</Layout.Vertical>
)
}
@ -149,7 +274,12 @@ const CheckMenuItem: React.FC<CheckMenuItemProps> = ({
setExpanded(isSelected)
}
}, [isSelected])
const name =
itemData?.uid &&
itemData?.uid.includes('-') &&
(itemData.payload?.kind as TypesCheckPayloadExtended) === CheckKindPayload.HARNESS_STAGE
? itemData.uid.split('-')[1]
: itemData.uid
return (
<Container className={css.menuItem}>
<Layout.Horizontal
@ -160,23 +290,23 @@ const CheckMenuItem: React.FC<CheckMenuItemProps> = ({
[css.forPipeline]: isPipeline
})}
{...ButtonRoleProps}
onClick={() => {
onClick={e => {
e.stopPropagation()
if (isPipeline) {
setExpanded(!expanded)
} else {
onClick()
}
}}>
<Render when={isPipeline}>
<NavArrowRight
color={Utils.getRealCSSColor(Color.GREY_500)}
className={cx(css.noShrink, css.chevron)}
strokeWidth="1.5"
/>
</Render>
<ExecutionStatus
className={cx(css.status, css.noShrink)}
status={itemData.status as ExecutionState}
iconSize={16}
noBackground
iconOnly
/>
<Text className={css.uid} lineClamp={1}>
{itemData.uid}
{name}
</Text>
<FlexExpander />
@ -185,13 +315,13 @@ const CheckMenuItem: React.FC<CheckMenuItemProps> = ({
{timeDistance(itemData.updated, itemData.created)}
</Text>
<ExecutionStatus
className={cx(css.status, css.noShrink)}
status={itemData.status as ExecutionState}
iconSize={16}
noBackground
iconOnly
/>
<Render when={isPipeline}>
<NavArrowRight
color={Utils.getRealCSSColor(Color.GREY_500)}
className={cx(css.noShrink, css.chevron)}
strokeWidth="1.5"
/>
</Render>
</Layout.Horizontal>
<Render when={isPipeline}>