Skip to content

Commit fa4c59d

Browse files
authored
feat: render proposal expired and partially executed states (#3036)
1 parent 3d2c142 commit fa4c59d

3 files changed

Lines changed: 53 additions & 8 deletions

File tree

‎src/modules/governance/StateBadge.tsx‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export enum ProposalBadgeState {
1616
Executed = 'Executed',
1717
Cancelled = 'Cancelled',
1818
Expired = 'Expired',
19+
PartiallyExecuted = 'Partially executed',
1920
}
2021

2122
type BadgeProps = {
@@ -52,6 +53,7 @@ const Badge = styled('span')<BadgeProps>(({ theme, state }) => {
5253
[ProposalBadgeState.Executed]: theme.palette.success.main,
5354
[ProposalBadgeState.Cancelled]: theme.palette.error.main,
5455
[ProposalBadgeState.Expired]: theme.palette.error.main,
56+
[ProposalBadgeState.PartiallyExecuted]: theme.palette.warning.main,
5557
[ProposalBadgeState.Failed]: theme.palette.error.main,
5658
};
5759
const color = COLOR_MAP[state] || '#000';
@@ -91,6 +93,8 @@ export const stateToString = (stateToString: ProposalBadgeState) => {
9193
return 'Cancelled';
9294
case ProposalBadgeState.Expired:
9395
return 'Expired';
96+
case ProposalBadgeState.PartiallyExecuted:
97+
return 'Partially executed';
9498
case ProposalBadgeState.Failed:
9599
return 'Failed';
96100
}
@@ -110,6 +114,8 @@ export const stringToState = (state: string) => {
110114
return ProposalBadgeState.Cancelled;
111115
case 'Expired':
112116
return ProposalBadgeState.Expired;
117+
case 'Partially executed':
118+
return ProposalBadgeState.PartiallyExecuted;
113119
case 'Failed':
114120
return ProposalBadgeState.Failed;
115121
}

‎src/modules/governance/adapters.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ export function cacheStateToBadge(state: string): ProposalBadgeState {
3535
return ProposalBadgeState.Failed;
3636
case 'cancelled':
3737
return ProposalBadgeState.Cancelled;
38+
case 'expired':
39+
return ProposalBadgeState.Expired;
40+
case 'partially_executed':
41+
return ProposalBadgeState.PartiallyExecuted;
3842
default:
3943
return ProposalBadgeState.Created;
4044
}

‎src/modules/governance/proposal/ProposalLifecycleCache.tsx‎

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,16 @@ export const ProposalLifecycleCache = ({
158158
}
159159

160160
const state = proposal.state;
161-
const stateOrder = ['created', 'active', 'queued', 'executed', 'failed', 'cancelled'];
161+
const stateOrder = [
162+
'created',
163+
'active',
164+
'queued',
165+
'executed',
166+
'partially_executed',
167+
'expired',
168+
'failed',
169+
'cancelled',
170+
];
162171
const currentStateIndex = stateOrder.indexOf(state);
163172

164173
// Build payload creation substeps
@@ -205,9 +214,12 @@ export const ProposalLifecycleCache = ({
205214
});
206215
}
207216
payloadExecutionSubsteps.push({
208-
stepName: `Payload ${p.payloadId} executed on ${getNetworkName(p.chainId)}`,
217+
stepName:
218+
p.state === 'expired'
219+
? `Payload ${p.payloadId} expired on ${getNetworkName(p.chainId)}`
220+
: `Payload ${p.payloadId} executed on ${getNetworkName(p.chainId)}`,
209221
timestamp: p.executedAt,
210-
completed: p.state === 'executed',
222+
completed: p.state === 'executed' || p.state === 'expired',
211223
active: false,
212224
networkLogo: getNetworkLogo(p.chainId),
213225
});
@@ -263,13 +275,36 @@ export const ProposalLifecycleCache = ({
263275

264276
// Add final state step
265277
const allPayloadsExecuted = !!payloads?.length && payloads.every((p) => p.state === 'executed');
278+
// proposal.executedAt is the dispatch time; payloads finish later after their timelock, so the
279+
// completed step should reflect the latest payload execution, matching the on-chain finish.
280+
const payloadExecutedTimes = (payloads ?? [])
281+
.map((p) => p.executedAt)
282+
.filter((t): t is string => !!t)
283+
.sort();
284+
const lastPayloadExecutedAt = payloadExecutedTimes[payloadExecutedTimes.length - 1] ?? null;
266285

267-
if (state === 'queued' || state === 'executed') {
286+
if (
287+
state === 'queued' ||
288+
state === 'executed' ||
289+
state === 'partially_executed' ||
290+
state === 'expired'
291+
) {
292+
const isTerminal = state === 'partially_executed' || state === 'expired';
293+
const finalStepName =
294+
state === 'expired'
295+
? 'Expired'
296+
: state === 'partially_executed'
297+
? 'Partially executed'
298+
: allPayloadsExecuted
299+
? 'Payloads executed'
300+
: 'Payload execution';
268301
steps.push({
269-
stepName: allPayloadsExecuted ? 'Payloads executed' : 'Payload execution',
270-
timestamp: allPayloadsExecuted ? proposal.executedAt : proposal.queuedAt,
271-
completed: allPayloadsExecuted,
272-
active: !allPayloadsExecuted,
302+
stepName: finalStepName,
303+
timestamp: allPayloadsExecuted
304+
? lastPayloadExecutedAt ?? proposal.executedAt
305+
: proposal.queuedAt,
306+
completed: isTerminal || allPayloadsExecuted,
307+
active: !isTerminal && !allPayloadsExecuted,
273308
lastStep: true,
274309
substeps: payloadExecutionSubsteps,
275310
});

0 commit comments

Comments
 (0)