Skip to content

Commit f0cecef

Browse files
committed
chore: display accurate execution and group states
1 parent 2ce440f commit f0cecef

File tree

11 files changed

+206
-60
lines changed

11 files changed

+206
-60
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { useMemo } from 'react'
2+
import { Flex, Tag } from '@chakra-ui/react'
3+
import startCase from 'lodash/startCase'
4+
5+
import { SingleSelect } from '../SingleSelect/SingleSelect'
6+
7+
export enum GroupStatusType {
8+
All = 'all',
9+
Success = 'success',
10+
Failure = 'failure',
11+
Waiting = 'waiting',
12+
}
13+
14+
interface GroupStatusFilterProps {
15+
statusFilter: GroupStatusType
16+
groupStats: { success: number; failure: number; waiting: number }
17+
setStatusFilter: (status: GroupStatusType) => void
18+
}
19+
20+
const getLabel = (status: GroupStatusType, count: number) => {
21+
let colorScheme = 'gray'
22+
if (status === GroupStatusType.Success) {
23+
colorScheme = 'success'
24+
} else if (status === GroupStatusType.Failure) {
25+
colorScheme = 'critical'
26+
} else if (status === GroupStatusType.Waiting) {
27+
colorScheme = 'gray'
28+
}
29+
return (
30+
<Flex alignItems="center" gap={4}>
31+
{startCase(status)} {getTag(colorScheme, count)}
32+
</Flex>
33+
)
34+
}
35+
36+
const getTag = (colorScheme: string, text: string | number) => {
37+
return (
38+
<Tag colorScheme={colorScheme} size="xs" borderRadius="lg">
39+
{text}
40+
</Tag>
41+
)
42+
}
43+
44+
export default function GroupStatusFilter({
45+
statusFilter,
46+
groupStats,
47+
setStatusFilter,
48+
}: GroupStatusFilterProps) {
49+
const items = useMemo(() => {
50+
const { failure, success, waiting } = groupStats
51+
const allCount = success + failure + waiting
52+
53+
return [
54+
{
55+
label: getLabel(GroupStatusType.All, allCount),
56+
value: GroupStatusType.All,
57+
},
58+
{
59+
label: getLabel(GroupStatusType.Success, success),
60+
value: GroupStatusType.Success,
61+
disabled: success === 0,
62+
},
63+
{
64+
label: getLabel(GroupStatusType.Failure, failure),
65+
value: GroupStatusType.Failure,
66+
disabled: failure === 0,
67+
},
68+
{
69+
label: getLabel(GroupStatusType.Waiting, waiting),
70+
value: GroupStatusType.Waiting,
71+
disabled: waiting === 0,
72+
},
73+
]
74+
}, [groupStats])
75+
76+
return (
77+
<>
78+
<SingleSelect
79+
items={items}
80+
isSearchable={false}
81+
onChange={(value) => setStatusFilter(value as GroupStatusType)}
82+
value={statusFilter}
83+
name="groupStatus"
84+
placeholder="Status"
85+
isClearable={false}
86+
colorScheme="secondary"
87+
/>
88+
</>
89+
)
90+
}
Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,54 @@
1+
// import { IExecution } from '@plumber/types'
2+
13
import { useMemo } from 'react'
2-
import { FaCheck, FaTimes } from 'react-icons/fa'
3-
import { Flex, Icon, Tag } from '@chakra-ui/react'
4+
import { Flex, Tag } from '@chakra-ui/react'
45

56
import { SingleSelect } from '@/components/SingleSelect'
67
import { type GroupedSteps } from '@/helpers/processExecutionSteps'
78

9+
// import { RetryAllButton } from '../ExecutionStep/components/RetryAllButton'
10+
import { GroupStatusType } from './GroupStatusFilter'
11+
812
interface IterationSelectorProps {
13+
// canRetryAll: boolean
14+
// execution: IExecution
915
groupedSteps: GroupedSteps
1016
selectedIteration: string
1117
setSelectedIteration: (iteration: string) => void
1218
}
1319

1420
export default function IterationSelector({
21+
// canRetryAll,
22+
// execution,
1523
groupedSteps,
1624
selectedIteration,
1725
setSelectedIteration,
1826
}: IterationSelectorProps) {
19-
const selectedIterationStep = groupedSteps[Number(selectedIteration)]
20-
const isSelectedIterationSuccessful =
21-
selectedIterationStep?.status === 'success'
22-
2327
const items = useMemo(() => {
2428
return groupedSteps.map(({ iteration, status }) => ({
25-
label: `Iteration ${iteration}`,
29+
label: (
30+
<Flex alignItems="center" gap={4}>
31+
Item {iteration}
32+
<Tag
33+
colorScheme={
34+
status === GroupStatusType.Waiting
35+
? 'gray'
36+
: status === GroupStatusType.Success
37+
? 'success'
38+
: 'critical'
39+
}
40+
size="xs"
41+
borderRadius="md"
42+
>
43+
{status === GroupStatusType.Waiting
44+
? 'Waiting'
45+
: status === GroupStatusType.Success
46+
? 'Success'
47+
: 'Failure'}
48+
</Tag>
49+
</Flex>
50+
),
2651
value: iteration.toString(),
27-
badge:
28-
status === 'success' ? (
29-
<Icon as={FaCheck} color="green" ml={4} />
30-
) : (
31-
<Icon as={FaTimes} color="red" ml={4} />
32-
),
3352
}))
3453
}, [groupedSteps])
3554

@@ -45,12 +64,8 @@ export default function IterationSelector({
4564
isClearable={false}
4665
colorScheme="secondary"
4766
/>
48-
<Tag
49-
colorScheme={isSelectedIterationSuccessful ? 'success' : 'critical'}
50-
size="lg"
51-
>
52-
{isSelectedIterationSuccessful ? 'Success' : 'Failure'}
53-
</Tag>
67+
{/* TODO: add retry buttons */}
68+
{/* {canRetryAll && <RetryAllButton execution={execution} type="iteration" />} */}
5469
</Flex>
5570
)
5671
}

packages/frontend/src/components/ExecutionGroup/index.tsx

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type { IExecution, IExecutionStep } from '@plumber/types'
22

33
import { useMemo, useState } from 'react'
44
import { Card, CardBody, Flex, Grid, HStack, Text } from '@chakra-ui/react'
5-
import { Tag } from '@opengovsg/design-system-react'
65
import get from 'lodash/get'
76

87
import { ExecutionStep } from '@/exports/components'
@@ -11,13 +10,14 @@ import { type GroupedSteps } from '@/helpers/processExecutionSteps'
1110
import AppIconWithStatus from '../ExecutionStep/components/AppIconWithStatus'
1211
import { useExecutionStepStatus } from '../ExecutionStep/hooks/useExecutionStepStatus'
1312

13+
import GroupStatusFilter, { GroupStatusType } from './GroupStatusFilter'
1414
import IterationSelector from './IterationSelector'
1515

1616
interface ExecutionGroupProps {
1717
execution: IExecution
1818
groupingStep: IExecutionStep
1919
groupedSteps: GroupedSteps
20-
groupStats: { success: number; failure: number }
20+
groupStats: { success: number; failure: number; waiting: number }
2121
numStepsBeforeGroup: number
2222
page: number
2323
}
@@ -33,24 +33,40 @@ export default function ExecutionGroup(props: ExecutionGroupProps) {
3333
} = props
3434
// NOTE: we use string here as the combobox value needs to be a string
3535
const [selectedIteration, setSelectedIteration] = useState('1')
36-
37-
const selectedIterationStep = useMemo(() => {
38-
return get(groupedSteps, Number(selectedIteration) - 1)
39-
}, [groupedSteps, selectedIteration])
36+
const [statusFilter, setStatusFilter] = useState<GroupStatusType>(
37+
GroupStatusType.All,
38+
)
4039

4140
const hasError = groupedSteps.some((iteration) =>
4241
iteration.steps.some((step) => step.errorDetails),
4342
)
4443
const allIterationsSuccessful = groupedSteps?.every(
45-
(iteration) => iteration.status === 'success',
44+
(step) => step.status === GroupStatusType.Success,
4645
)
4746

48-
// const canRetry = groupStats.failure > 0
47+
const iterationsToShow = useMemo(() => {
48+
let filteredSteps: GroupedSteps = groupedSteps
49+
if (statusFilter !== GroupStatusType.All) {
50+
filteredSteps = groupedSteps.filter(
51+
(iteration) => iteration.status === statusFilter,
52+
)
53+
}
54+
55+
setSelectedIteration(filteredSteps[0]?.iteration.toString() ?? '1')
56+
return filteredSteps
57+
}, [groupedSteps, statusFilter])
58+
59+
const selectedIterationStep = useMemo(() => {
60+
return get(groupedSteps, Number(selectedIteration) - 1)
61+
}, [groupedSteps, selectedIteration])
4962

5063
const { app, appName, statusIcon } = useExecutionStepStatus({
5164
appKey: groupingStep.appKey,
52-
stepKey: groupingStep.key,
53-
status: allIterationsSuccessful ? 'success' : 'failure',
65+
status: !execution.status
66+
? GroupStatusType.Waiting
67+
: allIterationsSuccessful
68+
? GroupStatusType.Success
69+
: GroupStatusType.Failure,
5470
errorDetails: hasError ? {} : null,
5571
execution,
5672
jobId: groupingStep.jobId,
@@ -85,27 +101,20 @@ export default function ExecutionGroup(props: ExecutionGroupProps) {
85101
{numStepsBeforeGroup + 1}. {appName}
86102
</Text>
87103
<Flex gap={2} alignItems="center">
88-
{groupStats.success > 0 && (
89-
<Tag colorScheme={'success'} size="lg">
90-
{groupStats.success} success
91-
</Tag>
92-
)}
93-
{groupStats.failure > 0 && (
94-
<Tag colorScheme={'critical'} size="lg">
95-
{groupStats.failure} failures
96-
</Tag>
97-
)}
98-
{/* TODO: add retry all iterations for this specific execution */}
99-
{/* {canRetry && (
100-
<RetryAllButton execution={execution} type="iteration" />
101-
)} */}
104+
<Flex gap={2} alignItems="center">
105+
<GroupStatusFilter
106+
groupStats={groupStats}
107+
setStatusFilter={setStatusFilter}
108+
statusFilter={statusFilter}
109+
/>
110+
</Flex>
102111
</Flex>
103112
</Flex>
104113
</HStack>
105114
</HStack>
106115
<Flex p={4} pt={0} direction="column" gap={4}>
107116
<IterationSelector
108-
groupedSteps={groupedSteps}
117+
groupedSteps={iterationsToShow}
109118
selectedIteration={selectedIteration}
110119
setSelectedIteration={setSelectedIteration}
111120
/>

packages/frontend/src/components/ExecutionStep/components/StatusIcons.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { BiSolidCheckCircle, BiSolidErrorCircle } from 'react-icons/bi'
2+
import { CgSandClock } from 'react-icons/cg'
23
import { Icon } from '@chakra-ui/react'
34

45
const successIcon = (
@@ -24,4 +25,8 @@ const partialIcon = (
2425
/>
2526
)
2627

27-
export { failureIcon, partialIcon, successIcon }
28+
const waitingIcon = (
29+
<Icon boxSize={6} as={CgSandClock} color="interaction.warning.default" />
30+
)
31+
32+
export { failureIcon, partialIcon, successIcon, waitingIcon }

packages/frontend/src/components/ExecutionStep/hooks/useExecutionStepStatus.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@ import type { IApp, IExecution } from '@plumber/types'
33
import { useMemo } from 'react'
44
import { useQuery } from '@apollo/client'
55

6+
import { GroupStatusType } from '@/components/ExecutionGroup/GroupStatusFilter'
67
import { GET_APP } from '@/graphql/queries/get-app'
78

89
import {
910
failureIcon,
1011
partialIcon,
1112
successIcon,
13+
waitingIcon,
1214
} from '../components/StatusIcons'
1315

1416
export interface UseExecutionStepStatusProps {
1517
appKey: string
16-
stepKey: string
1718
status?: string
1819
errorDetails?: any
1920
execution?: IExecution
@@ -33,7 +34,6 @@ export interface UseExecutionStepStatusReturn {
3334

3435
export function useExecutionStepStatus({
3536
appKey,
36-
// stepKey, // TODO: get more specific app name
3737
status,
3838
errorDetails,
3939
execution,
@@ -57,8 +57,11 @@ export function useExecutionStepStatus({
5757
if (isStepSuccessful) {
5858
return successIcon
5959
}
60+
if (status === GroupStatusType.Waiting) {
61+
return waitingIcon
62+
}
6063
return failureIcon
61-
}, [isPartialSuccess, isStepSuccessful])
64+
}, [isPartialSuccess, isStepSuccessful, status])
6265

6366
return {
6467
app,

packages/frontend/src/components/SingleSelect/components/DropdownItem/DropdownItem.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ export const DropdownItem = ({
5858
whiteSpace="nowrap"
5959
overflowX="hidden"
6060
>
61-
<DropdownItemTextHighlighter
62-
inputValue={inputValue ?? ''}
63-
textToHighlight={label}
64-
/>
61+
{typeof label === 'string' ? (
62+
<DropdownItemTextHighlighter
63+
inputValue={inputValue ?? ''}
64+
textToHighlight={label}
65+
/>
66+
) : (
67+
label
68+
)}
6569
</Text>
6670
{badge}
6771
</Flex>

packages/frontend/src/components/SingleSelect/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export type ComboboxItem<T = string> =
1010
*/
1111
value: T
1212
/** Label to render on input when selected. `value` will be used if this is not provided */
13-
label?: string
13+
label?: string | JSX.Element
1414
/** Description to render below label if provided */
1515
description?: string
1616
/** Whether item is disabled */

packages/frontend/src/components/SingleSelect/utils/itemUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const isItemAddNew = <Item extends ComboboxItem>(
2424

2525
export const itemToLabelString = <Item extends ComboboxItem>(
2626
item?: Item,
27-
): string => {
27+
): string | JSX.Element => {
2828
if (!item) {
2929
return ''
3030
}

packages/frontend/src/graphql/queries/get-execution-steps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export const GET_EXECUTION_STEPS = gql`
2626
key
2727
metadata {
2828
iteration
29+
isLastIteration
30+
isLastStep
2931
}
3032
}
3133
}

0 commit comments

Comments
 (0)