Merge branch 'Pipelines-list-improve' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#391)

This commit is contained in:
Dan Wilson 2023-09-04 19:51:27 +00:00 committed by Harness
commit 2ae31d4337
24 changed files with 256 additions and 210 deletions

View File

@ -9,7 +9,6 @@
.log { .log {
color: white; color: white;
font-family: Inconsolata, monospace; font-family: Inconsolata, monospace;
font-size: 2rem;
} }
.header { .header {

View File

@ -1,16 +1,16 @@
.logLayout { .logLayout {
margin-left: 2.3rem !important; margin-left: var(--spacing-xxxlarge) !important;
} }
.lineNumber { .lineNumber {
width: 1.5rem; width: var(--spacing-xlarge);
color: #999; color: #999;
margin-right: 1rem; margin-right: 16px;
font-family: 'Roboto Mono' !important; font-family: 'Roboto Mono' !important;
} }
.log { .log {
color: white !important; color: white !important;
margin-bottom: 1rem; margin-bottom: var(--spacing-medium);
font-family: 'Roboto Mono' !important; font-family: 'Roboto Mono' !important;
} }

View File

@ -5,6 +5,5 @@
} }
.loading { .loading {
margin-left: 2.3rem !important; margin-left: var(--spacing-xxxlarge) !important;
margin-top: 1.5rem !important;
} }

View File

@ -11,7 +11,7 @@
height: 100%; height: 100%;
.menuItem { .menuItem {
margin: 0.5rem 0 0.5rem 1rem !important; margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-medium) !important;
cursor: pointer; cursor: pointer;
&:not(:last-child) { &:not(:last-child) {
@ -35,8 +35,12 @@
.uid { .uid {
color: var(--grey-700) !important; color: var(--grey-700) !important;
font-weight: 600 !important; font-weight: 600 !important;
font-size: 1rem !important; font-size: var(--font-size-normal) !important;
} }
} }
} }
} }
.statusIcon {
align-self: center !important;
}

View File

@ -7,5 +7,6 @@ declare const styles: {
readonly layout: string readonly layout: string
readonly selected: string readonly selected: string
readonly uid: string readonly uid: string
readonly statusIcon: string
} }
export default styles export default styles

View File

@ -1,8 +1,9 @@
import React, { FC } from 'react' import React, { FC } from 'react'
import { Container, Layout, Text } from '@harnessio/uicore' import { Container, Layout, Text } from '@harnessio/uicore'
import { Icon } from '@harnessio/icons'
import cx from 'classnames' import cx from 'classnames'
import type { TypesStage } from 'services/code' import type { TypesStage } from 'services/code'
import { ExecutionState, ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import { getStatus } from 'utils/PipelineUtils'
import css from './ExecutionStageList.module.scss' import css from './ExecutionStageList.module.scss'
interface ExecutionStageListProps { interface ExecutionStageListProps {
@ -26,7 +27,13 @@ const ExecutionStage: FC<ExecutionStageProps> = ({ stage, isSelected = false, se
setSelectedStage(stage.number || null) setSelectedStage(stage.number || null)
}}> }}>
<Layout.Horizontal spacing="small" className={cx(css.layout, { [css.selected]: isSelected })}> <Layout.Horizontal spacing="small" className={cx(css.layout, { [css.selected]: isSelected })}>
<Icon name="success-tick" size={16} /> <ExecutionStatus
status={getStatus(stage.status || ExecutionState.PENDING)}
iconOnly
noBackground
iconSize={16}
className={css.statusIcon}
/>
<Text className={css.uid} lineClamp={1}> <Text className={css.uid} lineClamp={1}>
{stage.name} {stage.name}
</Text> </Text>

View File

@ -3,9 +3,9 @@ import { Text } from '@harnessio/uicore'
import type { IconName } from '@harnessio/icons' import type { IconName } from '@harnessio/icons'
import cx from 'classnames' import cx from 'classnames'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import css from './PRCheckExecutionStatus.module.scss' import css from './ExecutionStatus.module.scss'
export enum PRCheckExecutionState { export enum ExecutionState {
PENDING = 'pending', PENDING = 'pending',
RUNNING = 'running', RUNNING = 'running',
SUCCESS = 'success', SUCCESS = 'success',
@ -13,15 +13,15 @@ export enum PRCheckExecutionState {
ERROR = 'error' ERROR = 'error'
} }
interface PRCheckExecutionStatusProps { interface ExecutionStatusProps {
status: PRCheckExecutionState status: ExecutionState
iconOnly?: boolean iconOnly?: boolean
noBackground?: boolean noBackground?: boolean
iconSize?: number iconSize?: number
className?: string className?: string
} }
export const PRCheckExecutionStatus: React.FC<PRCheckExecutionStatusProps> = ({ export const ExecutionStatus: React.FC<ExecutionStatusProps> = ({
status, status,
iconSize = 20, iconSize = 20,
iconOnly = false, iconOnly = false,
@ -31,27 +31,27 @@ export const PRCheckExecutionStatus: React.FC<PRCheckExecutionStatusProps> = ({
const { getString } = useStrings() const { getString } = useStrings()
const maps = useMemo( const maps = useMemo(
() => ({ () => ({
[PRCheckExecutionState.PENDING]: { [ExecutionState.PENDING]: {
icon: 'ci-pending-build', icon: 'ci-pending-build',
css: css.pending, css: css.pending,
title: getString('pending').toLocaleUpperCase() title: getString('pending').toLocaleUpperCase()
}, },
[PRCheckExecutionState.RUNNING]: { [ExecutionState.RUNNING]: {
icon: 'running-filled', icon: 'running-filled',
css: css.running, css: css.running,
title: getString('running').toLocaleUpperCase() title: getString('running').toLocaleUpperCase()
}, },
[PRCheckExecutionState.SUCCESS]: { [ExecutionState.SUCCESS]: {
icon: 'execution-success', icon: 'execution-success',
css: css.success, css: css.success,
title: getString('success').toLocaleUpperCase() title: getString('success').toLocaleUpperCase()
}, },
[PRCheckExecutionState.FAILURE]: { [ExecutionState.FAILURE]: {
icon: 'error-transparent-no-outline', icon: 'error-transparent-no-outline',
css: css.failure, css: css.failure,
title: getString('failed').toLocaleUpperCase() title: getString('failed').toLocaleUpperCase()
}, },
[PRCheckExecutionState.ERROR]: { [ExecutionState.ERROR]: {
icon: 'solid-error', icon: 'solid-error',
css: css.error, css: css.error,
title: getString('error').toLocaleUpperCase() title: getString('error').toLocaleUpperCase()

View File

@ -314,9 +314,11 @@ export interface StringsMap {
payloadUrl: string payloadUrl: string
payloadUrlLabel: string payloadUrlLabel: string
pending: string pending: string
'pipelines.lastExecution': string
'pipelines.name': string 'pipelines.name': string
'pipelines.newPipelineButton': string 'pipelines.newPipelineButton': string
'pipelines.noData': string 'pipelines.noData': string
'pipelines.time': string
'pr.ableToMerge': string 'pr.ableToMerge': string
'pr.addDescription': string 'pr.addDescription': string
'pr.authorCommentedPR': string 'pr.authorCommentedPR': string

View File

@ -4,7 +4,7 @@ import { Color } from '@harnessio/design-system'
import type { GitInfoProps } from 'utils/GitUtils' import type { GitInfoProps } from 'utils/GitUtils'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { useListStatusCheckResults } from 'services/code' import { useListStatusCheckResults } from 'services/code'
import { PRCheckExecutionState } from 'components/PRCheckExecutionStatus/PRCheckExecutionStatus' import { ExecutionState } from 'components/ExecutionStatus/ExecutionStatus'
export function usePRChecksDecision({ export function usePRChecksDecision({
repoMetadata, repoMetadata,
@ -22,18 +22,18 @@ export function usePRChecksDecision({
const [message, setMessage] = useState('') const [message, setMessage] = useState('')
const [complete, setComplete] = useState(true) const [complete, setComplete] = useState(true)
const status = useMemo(() => { const status = useMemo(() => {
let _status: PRCheckExecutionState | undefined let _status: ExecutionState | undefined
const _count = { ...DEFAULT_COUNTS } const _count = { ...DEFAULT_COUNTS }
const total = data?.length const total = data?.length
if (total) { if (total) {
for (const check of data) { for (const check of data) {
switch (check.status) { switch (check.status) {
case PRCheckExecutionState.ERROR: case ExecutionState.ERROR:
case PRCheckExecutionState.FAILURE: case ExecutionState.FAILURE:
case PRCheckExecutionState.RUNNING: case ExecutionState.RUNNING:
case PRCheckExecutionState.PENDING: case ExecutionState.PENDING:
case PRCheckExecutionState.SUCCESS: case ExecutionState.SUCCESS:
_count[check.status]++ _count[check.status]++
setCount({ ..._count }) setCount({ ..._count })
break break
@ -44,27 +44,27 @@ export function usePRChecksDecision({
} }
if (_count.error) { if (_count.error) {
_status = PRCheckExecutionState.ERROR _status = ExecutionState.ERROR
setColor(Color.RED_900) setColor(Color.RED_900)
setBackground(Color.RED_50) setBackground(Color.RED_50)
setMessage(stringSubstitute(getString('prChecks.error'), { count: _count.error, total }) as string) setMessage(stringSubstitute(getString('prChecks.error'), { count: _count.error, total }) as string)
} else if (_count.failure) { } else if (_count.failure) {
_status = PRCheckExecutionState.FAILURE _status = ExecutionState.FAILURE
setColor(Color.RED_900) setColor(Color.RED_900)
setBackground(Color.RED_50) setBackground(Color.RED_50)
setMessage(stringSubstitute(getString('prChecks.failure'), { count: _count.failure, total }) as string) setMessage(stringSubstitute(getString('prChecks.failure'), { count: _count.failure, total }) as string)
} else if (_count.running) { } else if (_count.running) {
_status = PRCheckExecutionState.RUNNING _status = ExecutionState.RUNNING
setColor(Color.ORANGE_900) setColor(Color.ORANGE_900)
setBackground(Color.ORANGE_100) setBackground(Color.ORANGE_100)
setMessage(stringSubstitute(getString('prChecks.running'), { count: _count.running, total }) as string) setMessage(stringSubstitute(getString('prChecks.running'), { count: _count.running, total }) as string)
} else if (_count.pending) { } else if (_count.pending) {
_status = PRCheckExecutionState.PENDING _status = ExecutionState.PENDING
setColor(Color.GREY_600) setColor(Color.GREY_600)
setBackground(Color.GREY_100) setBackground(Color.GREY_100)
setMessage(stringSubstitute(getString('prChecks.pending'), { count: _count.pending, total }) as string) setMessage(stringSubstitute(getString('prChecks.pending'), { count: _count.pending, total }) as string)
} else if (_count.success) { } else if (_count.success) {
_status = PRCheckExecutionState.SUCCESS _status = ExecutionState.SUCCESS
setColor(Color.GREEN_800) setColor(Color.GREEN_800)
setBackground(Color.GREEN_50) setBackground(Color.GREEN_50)
setMessage(stringSubstitute(getString('prChecks.success'), { count: _count.success, total }) as string) setMessage(stringSubstitute(getString('prChecks.success'), { count: _count.success, total }) as string)

View File

@ -624,6 +624,8 @@ pipelines:
noData: There are no pipelines :( noData: There are no pipelines :(
newPipelineButton: New Pipeline newPipelineButton: New Pipeline
name: Pipeline Name name: Pipeline Name
time: Time
lastExecution: Last Execution
executions: executions:
noData: There are no executions :( noData: There are no executions :(
newExecutionButton: Run Pipeline newExecutionButton: Run Pipeline

View File

@ -7,25 +7,6 @@
} }
} }
.table {
[class*='TableV2--header'] [class*='variation-table-headers'] {
text-transform: none;
color: var(--grey-400);
font-weight: 500;
font-size: 13px;
}
.row {
height: 80px;
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
overflow: hidden;
&.noDesc > div {
height: 44px;
}
}
}
.withError { .withError {
display: grid; display: grid;
} }
@ -61,35 +42,30 @@
.number { .number {
color: var(--grey-400) !important; color: var(--grey-400) !important;
font-size: 0.875rem !important; font-size: var(--font-size-normal) !important;
font-weight: 500 !important; font-weight: 500 !important;
} }
.desc { .desc {
color: var(--grey-800) !important; color: var(--grey-800) !important;
font-size: 0.875rem !important; font-size: var(--font-size-normal) !important;
font-weight: 600 !important; font-weight: 600 !important;
} }
.author { .author {
color: var(--grey500) !important; color: var(--grey500) !important;
font-size: 0.6875rem !important; font-size: var(--font-size-small) !important;
font-weight: 600 !important; font-weight: 600 !important;
} }
.hash { .hash {
color: var(--primary-7) !important; color: var(--primary-7) !important;
font-family: Roboto Mono !important; font-family: Roboto Mono !important;
font-size: 0.75rem; font-size: var(--font-size-small) !important;
font-weight: 500; font-weight: 500 !important;
} }
.triggerLayout { .triggerLayout {
align-items: center !important; align-items: center !important;
} }
.divider {
color: var(--grey-300) !important;
font-size: 0.25rem !important;
}
} }

View File

@ -3,9 +3,6 @@
declare const styles: { declare const styles: {
readonly main: string readonly main: string
readonly layout: string readonly layout: string
readonly table: string
readonly row: string
readonly noDesc: string
readonly withError: string readonly withError: string
readonly nameContainer: string readonly nameContainer: string
readonly name: string readonly name: string
@ -15,6 +12,5 @@ declare const styles: {
readonly author: string readonly author: string
readonly hash: string readonly hash: string
readonly triggerLayout: string readonly triggerLayout: string
readonly divider: string
} }
export default styles export default styles

View File

@ -16,7 +16,6 @@ import cx from 'classnames'
import type { CellProps, Column } from 'react-table' import type { CellProps, Column } from 'react-table'
import { useHistory, useParams } from 'react-router-dom' import { useHistory, useParams } from 'react-router-dom'
import { useGet } from 'restful-react' import { useGet } from 'restful-react'
import { Icon } from '@harnessio/icons'
import { Timer, Calendar } from 'iconoir-react' import { Timer, Calendar } from 'iconoir-react'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
@ -30,6 +29,9 @@ import { usePageIndex } from 'hooks/usePageIndex'
import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination' import { ResourceListingPagination } from 'components/ResourceListingPagination/ResourceListingPagination'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader' import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
import { ExecutionState, ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import { getStatus } from 'utils/PipelineUtils'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import noExecutionImage from '../RepositoriesListing/no-repo.svg' import noExecutionImage from '../RepositoriesListing/no-repo.svg'
import css from './ExecutionList.module.scss' import css from './ExecutionList.module.scss'
@ -71,11 +73,14 @@ const ExecutionList = () => {
Cell: ({ row }: CellProps<TypesExecution>) => { Cell: ({ row }: CellProps<TypesExecution>) => {
const record = row.original const record = row.original
return ( return (
<Container className={css.nameContainer}> <Layout.Vertical className={css.nameContainer}>
<Layout.Vertical>
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }}> <Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }}>
{/* TODO this icon need to depend on the status */} <ExecutionStatus
<Icon name="success-tick" size={18} /> status={getStatus(record?.status || ExecutionState.PENDING)}
iconOnly
noBackground
iconSize={20}
/>
<Text className={css.number}>{`#${record.number}.`}</Text> <Text className={css.number}>{`#${record.number}.`}</Text>
<Text className={css.desc}>{record.title}</Text> <Text className={css.desc}>{record.title}</Text>
</Layout.Horizontal> </Layout.Horizontal>
@ -83,14 +88,13 @@ const ExecutionList = () => {
<Avatar email={record.author_email} name={record.author_name} size="small" hoverCard={false} /> <Avatar email={record.author_email} name={record.author_name} size="small" hoverCard={false} />
{/* TODO need logic here for different trigger types */} {/* TODO need logic here for different trigger types */}
<Text className={css.author}>{`${record.author_name} triggered manually`}</Text> <Text className={css.author}>{`${record.author_name} triggered manually`}</Text>
<Text className={css.divider}>{`|`}</Text> <PipeSeparator height={7} />
{/* TODO Will need to replace this with commit action - wont match Yifan designs */} {/* TODO Will need to replace this with commit component - wont match Yifan designs */}
<a rel="noreferrer noopener" className={css.hash}> <a rel="noreferrer noopener" className={css.hash}>
{record.after} {record.after}
</a> </a>
</Layout.Horizontal> </Layout.Horizontal>
</Layout.Vertical> </Layout.Vertical>
</Container>
) )
} }
}, },
@ -158,7 +162,6 @@ const ExecutionList = () => {
<Container margin={{ top: 'medium' }}> <Container margin={{ top: 'medium' }}>
{!!executions?.length && ( {!!executions?.length && (
<Table<TypesExecution> <Table<TypesExecution>
className={css.table}
columns={columns} columns={columns}
data={executions || []} data={executions || []}
onRowClick={executionInfo => onRowClick={executionInfo =>
@ -170,7 +173,6 @@ const ExecutionList = () => {
}) })
) )
} }
getRowClassName={row => cx(css.row, !row.original.number && css.noDesc)}
/> />
)} )}

View File

@ -11,76 +11,49 @@
display: grid; display: grid;
} }
.table {
[class*='TableV2--header'] [class*='variation-table-headers'] {
text-transform: none;
color: var(--grey-400);
font-weight: 500;
font-size: 13px;
}
.row {
height: 80px;
box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
overflow: hidden;
&.noDesc > div {
height: 44px;
}
}
}
.nameContainer { .nameContainer {
position: relative; display: flex !important;
align-items: center !important;
.name { }
flex-grow: 1;
align-items: baseline !important; .repoName {
width: calc(100% - 100px) !important; font-weight: 600 !important;
font-size: var(--font-size-normal) !important;
> span { color: var(--grey-700) !important;
width: 100%; }
> span {
width: 100%; .desc {
} color: var(--grey-700) !important;
} font-size: var(--font-size-small) !important;
font-style: normal !important;
& + span:last-of-type { font-weight: 600 !important;
align-self: center; }
}
} .avatar {
margin: 0 0 0 -3px !important;
.pinned { }
transform: rotate(-90deg);
position: absolute; .author {
top: 7px; color: var(--grey500) !important;
left: -43px; font-size: var(--font-size-small) !important;
font-size: var(--font-size-xsmall) !important; font-weight: 500 !important;
padding: 6px 14px; }
}
.hash {
.repoName { color: var(--primary-7) !important;
font-weight: 600 !important; font-family: Roboto Mono !important;
font-size: 16px !important; font-size: var(--font-size-small) !important;
line-height: 24px !important; font-weight: 500 !important;
color: var(--grey-800); }
.repoScope { .triggerLayout {
color: var(--grey-400); align-items: center !important;
padding: 2px 6px; }
font-size: var(--font-size-xsmall) !important;
border-radius: 4px; .spacer {
border: 1px solid var(--grey-200); width: 180px;
display: inline-block; }
margin-left: var(--spacing-medium);
text-transform: uppercase; .statusIcon {
line-height: 16px; align-self: center !important;
}
}
.desc {
color: var(--grey-500);
font-size: var(--font-size-small);
padding-top: var(--spacing-xsmall) !important;
}
} }

View File

@ -4,14 +4,14 @@ declare const styles: {
readonly main: string readonly main: string
readonly layout: string readonly layout: string
readonly withError: string readonly withError: string
readonly table: string
readonly row: string
readonly noDesc: string
readonly nameContainer: string readonly nameContainer: string
readonly name: string
readonly pinned: string
readonly repoName: string readonly repoName: string
readonly repoScope: string
readonly desc: string readonly desc: string
readonly avatar: string
readonly author: string
readonly hash: string
readonly triggerLayout: string
readonly spacer: string
readonly statusIcon: string
} }
export default styles export default styles

View File

@ -1,5 +1,6 @@
import React, { useMemo, useState } from 'react' import React, { useMemo, useState } from 'react'
import { import {
Avatar,
Button, Button,
ButtonVariation, ButtonVariation,
Container, Container,
@ -7,7 +8,8 @@ import {
Layout, Layout,
PageBody, PageBody,
TableV2 as Table, TableV2 as Table,
Text Text,
Utils
} from '@harnessio/uicore' } from '@harnessio/uicore'
import { Color } from '@harnessio/design-system' import { Color } from '@harnessio/design-system'
import cx from 'classnames' import cx from 'classnames'
@ -15,11 +17,12 @@ import type { CellProps, Column } from 'react-table'
import Keywords from 'react-keywords' import Keywords from 'react-keywords'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'
import { useGet } from 'restful-react' import { useGet } from 'restful-react'
import { Calendar, Timer, GitFork } from 'iconoir-react'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner' import { SearchInputWithSpinner } from 'components/SearchInputWithSpinner/SearchInputWithSpinner'
import { NoResultCard } from 'components/NoResultCard/NoResultCard' import { NoResultCard } from 'components/NoResultCard/NoResultCard'
import { LIST_FETCHING_LIMIT, PageBrowserProps, formatDate, getErrorMessage, voidFn } from 'utils/Utils' import { LIST_FETCHING_LIMIT, PageBrowserProps, getErrorMessage, timeDistance, voidFn } from 'utils/Utils'
import type { TypesPipeline } from 'services/code' import type { TypesPipeline } from 'services/code'
import { useQueryParams } from 'hooks/useQueryParams' import { useQueryParams } from 'hooks/useQueryParams'
import { usePageIndex } from 'hooks/usePageIndex' import { usePageIndex } from 'hooks/usePageIndex'
@ -27,6 +30,9 @@ import { ResourceListingPagination } from 'components/ResourceListingPagination/
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata' import { useGetRepositoryMetadata } from 'hooks/useGetRepositoryMetadata'
import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader' import { RepositoryPageHeader } from 'components/RepositoryPageHeader/RepositoryPageHeader'
import { ExecutionStatus, ExecutionState } from 'components/ExecutionStatus/ExecutionStatus'
import { getStatus } from 'utils/PipelineUtils'
import { PipeSeparator } from 'components/PipeSeparator/PipeSeparator'
import noPipelineImage from '../RepositoriesListing/no-repo.svg' import noPipelineImage from '../RepositoriesListing/no-repo.svg'
import css from './PipelineList.module.scss' import css from './PipelineList.module.scss'
@ -48,8 +54,9 @@ const PipelineList = () => {
response response
} = useGet<TypesPipeline[]>({ } = useGet<TypesPipeline[]>({
path: `/api/v1/repos/${repoMetadata?.path}/+/pipelines`, path: `/api/v1/repos/${repoMetadata?.path}/+/pipelines`,
queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm }, queryParams: { page, limit: LIST_FETCHING_LIMIT, query: searchTerm, latest: true },
lazy: !repoMetadata lazy: !repoMetadata,
debounce: 500
}) })
const NewPipelineButton = ( const NewPipelineButton = (
@ -66,33 +73,87 @@ const PipelineList = () => {
() => [ () => [
{ {
Header: getString('pipelines.name'), Header: getString('pipelines.name'),
width: 'calc(100% - 180px)', width: 'calc(50% - 90px)',
Cell: ({ row }: CellProps<TypesPipeline>) => { Cell: ({ row }: CellProps<TypesPipeline>) => {
const record = row.original const record = row.original
return ( return (
<Container className={css.nameContainer}> <Layout.Horizontal spacing="small" className={css.nameContainer}>
<Layout.Horizontal spacing="small" style={{ flexGrow: 1 }}> <ExecutionStatus
<Layout.Vertical flex className={css.name}> status={getStatus(record?.execution?.status || ExecutionState.PENDING)}
iconOnly
noBackground
iconSize={24}
className={css.statusIcon}
/>
<Text className={css.repoName}> <Text className={css.repoName}>
<Keywords value={searchTerm}>{record.uid}</Keywords> <Keywords value={searchTerm}>{record.uid}</Keywords>
</Text> </Text>
{record.description && <Text className={css.desc}>{record.description}</Text>}
</Layout.Vertical>
</Layout.Horizontal> </Layout.Horizontal>
</Container>
) )
} }
}, },
{ {
Header: getString('repos.updated'), Header: getString('pipelines.lastExecution'),
width: 'calc(50% - 90px)',
Cell: ({ row }: CellProps<TypesPipeline>) => {
const record = row.original.execution
return record ? (
<Layout.Vertical spacing={'small'}>
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }}>
<Text className={css.desc}>{`#${record.number}`}</Text>
<PipeSeparator height={7} />
<Text className={css.desc}>{record.title}</Text>
</Layout.Horizontal>
<Layout.Horizontal spacing={'xsmall'} style={{ alignItems: 'center' }}>
<Avatar
email={record.author_email}
name={record.author_name}
size="small"
hoverCard={false}
className={css.avatar}
/>
{/* TODO need logic here for different trigger types */}
<Text className={css.author}>{record.author_name}</Text>
<PipeSeparator height={7} />
<GitFork height={12} width={12} color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text className={css.author}>{record.source}</Text>
<PipeSeparator height={7} />
{/* TODO Will need to replace this with commit component - wont match Yifan designs */}
<a rel="noreferrer noopener" className={css.hash}>
{/* {record.after} */}
hardcoded
</a>
</Layout.Horizontal>
</Layout.Vertical>
) : (
<div className={css.spacer} />
)
}
},
{
Header: getString('pipelines.time'),
width: '180px', width: '180px',
Cell: ({ row }: CellProps<TypesPipeline>) => { Cell: ({ row }: CellProps<TypesPipeline>) => {
return ( const record = row.original.execution
<Layout.Horizontal style={{ alignItems: 'center' }}>
<Text color={Color.BLACK} lineClamp={1} rightIconProps={{ size: 10 }} width={120}> return record ? (
{formatDate(row.original.updated as number)} <Layout.Vertical spacing={'small'}>
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }}>
<Timer color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} lineClamp={1} width={180} font={{ size: 'small' }}>
{timeDistance(record.started, record.finished)}
</Text> </Text>
</Layout.Horizontal> </Layout.Horizontal>
<Layout.Horizontal spacing={'small'} style={{ alignItems: 'center' }}>
<Calendar color={Utils.getRealCSSColor(Color.GREY_500)} />
<Text inline color={Color.GREY_500} lineClamp={1} width={180} font={{ size: 'small' }}>
{timeDistance(record.finished, Date.now())} ago
</Text>
</Layout.Horizontal>
</Layout.Vertical>
) : (
<div className={css.spacer} />
) )
}, },
disableSortBy: true disableSortBy: true
@ -133,7 +194,6 @@ const PipelineList = () => {
<Container margin={{ top: 'medium' }}> <Container margin={{ top: 'medium' }}>
{!!pipelines?.length && ( {!!pipelines?.length && (
<Table<TypesPipeline> <Table<TypesPipeline>
className={css.table}
columns={columns} columns={columns}
data={pipelines || []} data={pipelines || []}
onRowClick={pipelineInfo => onRowClick={pipelineInfo =>
@ -144,7 +204,6 @@ const PipelineList = () => {
}) })
) )
} }
getRowClassName={row => cx(css.row, !row.original.description && css.noDesc)}
/> />
)} )}

View File

@ -27,7 +27,7 @@ import { useStrings } from 'framework/strings'
import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer' import { MarkdownViewer } from 'components/MarkdownViewer/MarkdownViewer'
import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision' import type { PRChecksDecisionResult } from 'hooks/usePRChecksDecision'
import type { TypesCheck } from 'services/code' import type { TypesCheck } from 'services/code'
import { PRCheckExecutionState, PRCheckExecutionStatus } from 'components/PRCheckExecutionStatus/PRCheckExecutionStatus' import { ExecutionState, ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import css from './Checks.module.scss' import css from './Checks.module.scss'
interface ChecksProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> { interface ChecksProps extends Pick<GitInfoProps, 'repoMetadata' | 'pullRequestMetadata'> {
@ -77,11 +77,11 @@ export const Checks: React.FC<ChecksProps> = props => {
<Render when={selectedItemData}> <Render when={selectedItemData}>
<Container className={css.header}> <Container className={css.header}>
<Layout.Horizontal className={css.headerLayout} spacing="small"> <Layout.Horizontal className={css.headerLayout} spacing="small">
<PRCheckExecutionStatus <ExecutionStatus
className={cx(css.status, { className={cx(css.status, {
[css.invert]: selectedItemData?.status === PRCheckExecutionState.PENDING [css.invert]: selectedItemData?.status === ExecutionState.PENDING
})} })}
status={selectedItemData?.status as PRCheckExecutionState} status={selectedItemData?.status as ExecutionState}
iconSize={20} iconSize={20}
noBackground noBackground
iconOnly iconOnly
@ -162,11 +162,11 @@ const ChecksMenu: React.FC<ChecksMenuProps> = ({
} else { } else {
// Find and set a default selected item. Order: Error, Failure, Running, Success, Pending // Find and set a default selected item. Order: Error, Failure, Running, Success, Pending
const defaultSelectedItem = const defaultSelectedItem =
prChecksDecisionResult?.data?.find(({ status }) => status === PRCheckExecutionState.ERROR) || prChecksDecisionResult?.data?.find(({ status }) => status === ExecutionState.ERROR) ||
prChecksDecisionResult?.data?.find(({ status }) => status === PRCheckExecutionState.FAILURE) || prChecksDecisionResult?.data?.find(({ status }) => status === ExecutionState.FAILURE) ||
prChecksDecisionResult?.data?.find(({ status }) => status === PRCheckExecutionState.RUNNING) || prChecksDecisionResult?.data?.find(({ status }) => status === ExecutionState.RUNNING) ||
prChecksDecisionResult?.data?.find(({ status }) => status === PRCheckExecutionState.SUCCESS) || prChecksDecisionResult?.data?.find(({ status }) => status === ExecutionState.SUCCESS) ||
prChecksDecisionResult?.data?.find(({ status }) => status === PRCheckExecutionState.PENDING) || prChecksDecisionResult?.data?.find(({ status }) => status === ExecutionState.PENDING) ||
prChecksDecisionResult?.data?.[0] prChecksDecisionResult?.data?.[0]
if (defaultSelectedItem) { if (defaultSelectedItem) {
@ -266,9 +266,9 @@ const CheckMenuItem: React.FC<CheckMenuItemProps> = ({ expandable, isSelected =
{timeDistance(itemData.updated, itemData.created)} {timeDistance(itemData.updated, itemData.created)}
</Text> </Text>
<PRCheckExecutionStatus <ExecutionStatus
className={cx(css.status, css.noShrink)} className={cx(css.status, css.noShrink)}
status={itemData.status as PRCheckExecutionState} status={itemData.status as ExecutionState}
iconSize={16} iconSize={16}
noBackground noBackground
iconOnly iconOnly

View File

@ -5,7 +5,7 @@ import { FontVariation } from '@harnessio/design-system'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import type { GitInfoProps } from 'utils/GitUtils' import type { GitInfoProps } from 'utils/GitUtils'
import { useStrings } from 'framework/strings' import { useStrings } from 'framework/strings'
import { PRCheckExecutionStatus, PRCheckExecutionState } from 'components/PRCheckExecutionStatus/PRCheckExecutionStatus' import { ExecutionStatus, ExecutionState } from 'components/ExecutionStatus/ExecutionStatus'
import { useShowRequestError } from 'hooks/useShowRequestError' import { useShowRequestError } from 'hooks/useShowRequestError'
import type { TypesCheck } from 'services/code' import type { TypesCheck } from 'services/code'
import { useAppContext } from 'AppContext' import { useAppContext } from 'AppContext'
@ -50,7 +50,7 @@ export function ChecksOverview({ repoMetadata, pullRequestMetadata, prChecksDeci
</Truthy> </Truthy>
<Falsy> <Falsy>
<Layout.Horizontal spacing="small" className={css.layout}> <Layout.Horizontal spacing="small" className={css.layout}>
<PRCheckExecutionStatus status={overallStatus} noBackground iconOnly /> <ExecutionStatus status={overallStatus} noBackground iconOnly />
<Text font={{ variation: FontVariation.LEAD }}>{getString('pr.checks')}</Text> <Text font={{ variation: FontVariation.LEAD }}>{getString('pr.checks')}</Text>
<Text color={color} padding={{ left: 'small' }} font={{ variation: FontVariation.FORM_MESSAGE_WARNING }}> <Text color={color} padding={{ left: 'small' }} font={{ variation: FontVariation.FORM_MESSAGE_WARNING }}>
{message} {message}
@ -94,7 +94,7 @@ const CheckSection: React.FC<CheckSectionProps> = ({ repoMetadata, pullRequestMe
<Container className={css.row} key={uid}> <Container className={css.row} key={uid}>
<Layout.Horizontal className={css.rowLayout}> <Layout.Horizontal className={css.rowLayout}>
<Container className={css.status}> <Container className={css.status}>
<PRCheckExecutionStatus status={status as PRCheckExecutionState} /> <ExecutionStatus status={status as ExecutionState} />
</Container> </Container>
<Link <Link

View File

@ -15,7 +15,7 @@ import type { TypesPullReq, TypesPullReqStats, TypesRepository } from 'services/
import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner/LoadingSpinner'
import { TabTitleWithCount, tabContainerCSS } from 'components/TabTitleWithCount/TabTitleWithCount' import { TabTitleWithCount, tabContainerCSS } from 'components/TabTitleWithCount/TabTitleWithCount'
import { usePRChecksDecision } from 'hooks/usePRChecksDecision' import { usePRChecksDecision } from 'hooks/usePRChecksDecision'
import { PRCheckExecutionStatus } from 'components/PRCheckExecutionStatus/PRCheckExecutionStatus' import { ExecutionStatus } from 'components/ExecutionStatus/ExecutionStatus'
import { PullRequestMetaLine } from './PullRequestMetaLine' import { PullRequestMetaLine } from './PullRequestMetaLine'
import { Conversation } from './Conversation/Conversation' import { Conversation } from './Conversation/Conversation'
import { Checks } from './Checks/Checks' import { Checks } from './Checks/Checks'
@ -248,7 +248,7 @@ export default function PullRequest() {
prChecksDecisionResult?.overallStatus ? ( prChecksDecisionResult?.overallStatus ? (
<Container className={css.checksCount}> <Container className={css.checksCount}>
<Layout.Horizontal className={css.checksCountLayout}> <Layout.Horizontal className={css.checksCountLayout}>
<PRCheckExecutionStatus <ExecutionStatus
status={prChecksDecisionResult?.overallStatus} status={prChecksDecisionResult?.overallStatus}
noBackground noBackground
iconOnly iconOnly

View File

@ -634,12 +634,12 @@ export interface TypesPipeline {
created?: number created?: number
default_branch?: string default_branch?: string
description?: string description?: string
execution?: TypesExecution
id?: number id?: number
repo_id?: number repo_id?: number
seq?: number seq?: number
uid?: string uid?: string
updated?: number updated?: number
version?: number
} }
export interface TypesPlugin { export interface TypesPlugin {
@ -2239,6 +2239,10 @@ export interface ListPipelinesQueryParams {
* The maximum number of results to return. * The maximum number of results to return.
*/ */
limit?: number limit?: number
/**
* Whether to fetch latest build information for each pipeline.
*/
latest?: boolean
} }
export interface ListPipelinesPathParams { export interface ListPipelinesPathParams {

View File

@ -1971,6 +1971,12 @@ paths:
maximum: 100 maximum: 100
minimum: 1 minimum: 1
type: integer type: integer
- description: Whether to fetch latest build information for each pipeline.
in: query
name: latest
required: false
schema:
type: boolean
- in: path - in: path
name: repo_ref name: repo_ref
required: true required: true
@ -2010,7 +2016,7 @@ paths:
$ref: '#/components/schemas/UsererrorError' $ref: '#/components/schemas/UsererrorError'
description: Internal Server Error description: Internal Server Error
tags: tags:
- repos - pipeline
post: post:
operationId: createPipeline operationId: createPipeline
parameters: parameters:
@ -7231,6 +7237,8 @@ components:
type: string type: string
description: description:
type: string type: string
execution:
$ref: '#/components/schemas/TypesExecution'
id: id:
type: integer type: integer
repo_id: repo_id:
@ -7241,8 +7249,6 @@ components:
type: string type: string
updated: updated:
type: integer type: integer
version:
type: integer
type: object type: object
TypesPlugin: TypesPlugin:
properties: properties:

View File

@ -0,0 +1,16 @@
import { ExecutionState } from 'components/ExecutionStatus/ExecutionStatus'
export const getStatus = (status: string): ExecutionState => {
switch (status) {
case 'success':
return ExecutionState.SUCCESS
case 'failed':
return ExecutionState.FAILURE
case 'running':
return ExecutionState.RUNNING
case 'pending':
return ExecutionState.PENDING
default:
return ExecutionState.PENDING
}
}