Skip to content

Commit 162f033

Browse files
authored
History notifications not being removed (#2151)
Signed-off-by: John Bair <john.bair@swirldslabs.com>
1 parent 6b9594a commit 162f033

File tree

7 files changed

+138
-87
lines changed

7 files changed

+138
-87
lines changed

front-end/src/renderer/composables/useFilterNotifications.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import useNotificationsStore from '@renderer/stores/storeNotifications.ts';
88

99
export default function useFilterNotifications(
1010
transactionNode: Ref<ITransactionNode>,
11-
notificationType: Ref<NotificationType|null>,
11+
notificationTypes: Ref<NotificationType[]>,
1212
) {
1313

1414
/* Stores */
@@ -22,16 +22,18 @@ export default function useFilterNotifications(
2222
const filteredNotificationIds = computed(() => {
2323
return filteredNotifications.value.map(candidate => candidate.id);
2424
});
25+
2526
const notificationsKey = computed(() => user.selectedOrganization?.serverUrl ?? '');
27+
2628
const candidateNotifications = computed(() => {
2729
let result: INotificationReceiver[];
28-
if (notificationType.value !== null) {
30+
if (notificationTypes.value.length > 0) {
2931
const serverNotifications = notifications.notifications[notificationsKey.value] ?? [];
3032
result = serverNotifications.filter((n: INotificationReceiver) => {
31-
return n.notification.type === notificationType.value;
33+
return notificationTypes.value.includes(n.notification.type);
3234
});
3335
} else {
34-
result = []
36+
result = [];
3537
}
3638
return result;
3739
});
@@ -85,7 +87,7 @@ export default function useFilterNotifications(
8587
/* Mount */
8688
onMounted(() => {
8789
watch(
88-
[user.selectedOrganization, notifications.notifications, transactionNode, notificationType],
90+
[user.selectedOrganization, notifications.notifications, transactionNode, notificationTypes],
8991
updateFilteredNotifications,
9092
{ immediate: true },
9193
);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { onBeforeMount } from 'vue';
2+
import useUserStore from '@renderer/stores/storeUser';
3+
import useWebsocketConnection from '@renderer/stores/storeWebsocketConnection';
4+
import useDisposableWs from '@renderer/composables/useDisposableWs';
5+
6+
/**
7+
* Subscribe to a WebSocket event for the current organization
8+
* Automatically handles initial connection and cleanup
9+
*
10+
* @param eventName - The WebSocket event to listen for
11+
* @param callback - Function to call when event is received
12+
*/
13+
export default function useWebsocketSubscription(
14+
eventName: string,
15+
callback: () => void | Promise<void>,
16+
) {
17+
const user = useUserStore();
18+
const wsStore = useWebsocketConnection();
19+
const ws = useDisposableWs();
20+
21+
/* Functions */
22+
const subscribe = () => {
23+
if (!user.selectedOrganization?.serverUrl) return;
24+
ws.on(user.selectedOrganization.serverUrl, eventName, callback);
25+
};
26+
27+
/* Subscribe immediately on mount (in case WS is already connected) */
28+
onBeforeMount(subscribe);
29+
30+
/* Handle WebSocket reconnections (for future reconnects) */
31+
wsStore.$onAction(ctx => {
32+
if (ctx.name !== 'setup') return;
33+
ctx.after(() => subscribe());
34+
});
35+
}

front-end/src/renderer/pages/TransactionDetails/TransactionDetails.vue

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ import { CommonNetwork } from '@shared/enums';
1414
import useUserStore from '@renderer/stores/storeUser';
1515
import useNetwork from '@renderer/stores/storeNetwork';
1616
import useContactsStore from '@renderer/stores/storeContacts';
17-
import useWebsocketConnection from '@renderer/stores/storeWebsocketConnection';
1817
import useNextTransactionStore from '@renderer/stores/storeNextTransaction';
1918
20-
import useDisposableWs from '@renderer/composables/useDisposableWs';
2119
import useSetDynamicLayout, { LOGGED_IN_LAYOUT } from '@renderer/composables/useSetDynamicLayout';
20+
import useWebsocketSubscription from '@renderer/composables/useWebsocketSubscription';
2221
2322
import { getApiGroupById, getTransactionById } from '@renderer/services/organization';
2423
import { getTransaction } from '@renderer/services/transactionService';
@@ -58,12 +57,20 @@ import TransactionId from '@renderer/components/ui/TransactionId.vue';
5857
const user = useUserStore();
5958
const network = useNetwork();
6059
const contacts = useContactsStore();
61-
const wsStore = useWebsocketConnection();
6260
const nextTransaction = useNextTransactionStore();
6361
6462
/* Composables */
6563
const router = useRouter();
66-
const ws = useDisposableWs();
64+
useWebsocketSubscription(TRANSACTION_ACTION, async () => {
65+
await fetchTransaction();
66+
const id = formattedId.value!;
67+
nextId.value = await nextTransaction.getNext(
68+
isLoggedInOrganization(user.selectedOrganization) ? Number(id) : id,
69+
);
70+
prevId.value = await nextTransaction.getPrevious(
71+
isLoggedInOrganization(user.selectedOrganization) ? Number(id) : id,
72+
);
73+
});
6774
useSetDynamicLayout(LOGGED_IN_LAYOUT);
6875
const route = useRoute();
6976
@@ -158,20 +165,6 @@ async function fetchTransaction() {
158165
feePayer.value = getTransactionPayerId(sdkTransaction.value);
159166
}
160167
161-
const subscribeToTransactionAction = () => {
162-
if (!user.selectedOrganization?.serverUrl) return;
163-
ws.on(user.selectedOrganization?.serverUrl, TRANSACTION_ACTION, async () => {
164-
await fetchTransaction();
165-
const id = formattedId.value!;
166-
nextId.value = await nextTransaction.getNext(
167-
isLoggedInOrganization(user.selectedOrganization) ? Number(id) : id,
168-
);
169-
prevId.value = await nextTransaction.getPrevious(
170-
isLoggedInOrganization(user.selectedOrganization) ? Number(id) : id,
171-
);
172-
});
173-
};
174-
175168
const formattedId = computed(() => {
176169
const id = router.currentRoute.value.params.id;
177170
return id ? (Array.isArray(id) ? id[0] : id) : null;
@@ -189,8 +182,6 @@ onBeforeMount(async () => {
189182
const keepNextTransaction = router.currentRoute.value.query[KEEP_NEXT_QUERY_KEY];
190183
if (!keepNextTransaction) nextTransaction.reset();
191184
192-
subscribeToTransactionAction();
193-
194185
const result = await Promise.all([
195186
fetchTransaction(),
196187
nextTransaction.getNext(
@@ -215,11 +206,6 @@ onBeforeRouteLeave(to => {
215206
});
216207
217208
/* Watchers */
218-
wsStore.$onAction(ctx => {
219-
if (ctx.name !== 'setup') return;
220-
ctx.after(() => subscribeToTransactionAction());
221-
});
222-
223209
watch(() => user.selectedOrganization, router.back);
224210
225211
watch(feePayer, async newFeePayer => {

front-end/src/renderer/pages/TransactionGroupDetails/TransactionGroupDetails.vue

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,12 @@ import { historyTitle, TRANSACTION_ACTION } from '@shared/constants';
1414
1515
import useUserStore from '@renderer/stores/storeUser';
1616
import useNetwork from '@renderer/stores/storeNetwork';
17-
import useWebsocketConnection from '@renderer/stores/storeWebsocketConnection';
1817
import useNextTransactionStore from '@renderer/stores/storeNextTransaction';
1918
20-
import useDisposableWs from '@renderer/composables/useDisposableWs';
2119
import usePersonalPassword from '@renderer/composables/usePersonalPassword';
2220
import useSetDynamicLayout, { LOGGED_IN_LAYOUT } from '@renderer/composables/useSetDynamicLayout';
2321
import useCreateTooltips from '@renderer/composables/useCreateTooltips';
22+
import useWebsocketSubscription from '@renderer/composables/useWebsocketSubscription';
2423
2524
import { areByteArraysEqual } from '@shared/utils/byteUtils';
2625
@@ -86,15 +85,18 @@ const buttonsDataTestIds: { [key: string]: string } = {
8685
/* Stores */
8786
const user = useUserStore();
8887
const network = useNetwork();
89-
const wsStore = useWebsocketConnection();
9088
const nextTransaction = useNextTransactionStore();
9189
const contacts = useContactsStore();
9290
9391
/* Composables */
9492
const router = useRouter();
9593
const route = useRoute();
9694
const toast = useToast();
97-
const ws = useDisposableWs();
95+
useWebsocketSubscription(TRANSACTION_ACTION, async () => {
96+
const id = router.currentRoute.value.params.id;
97+
await fetchGroup(Array.isArray(id) ? id[0] : id);
98+
setGetTransactionsFunction();
99+
});
98100
useSetDynamicLayout(LOGGED_IN_LAYOUT);
99101
const { getPassword, passwordModalOpened } = usePersonalPassword();
100102
const createTooltips = useCreateTooltips();
@@ -482,17 +484,11 @@ onBeforeMount(async () => {
482484
return;
483485
}
484486
485-
subscribeToTransactionAction();
486487
await fetchGroup(Array.isArray(id) ? id[0] : id);
487488
setGetTransactionsFunction();
488489
});
489490
490491
/* Watchers */
491-
wsStore.$onAction(ctx => {
492-
if (ctx.name !== 'setup') return;
493-
ctx.after(() => subscribeToTransactionAction());
494-
});
495-
496492
watch(
497493
() => user.selectedOrganization,
498494
() => {
@@ -576,15 +572,6 @@ const isTransactionInProgress = (transaction: ITransactionFull) => {
576572
].includes(transaction.status);
577573
};
578574
579-
const subscribeToTransactionAction = () => {
580-
if (!user.selectedOrganization?.serverUrl) return;
581-
ws.on(user.selectedOrganization?.serverUrl, TRANSACTION_ACTION, async () => {
582-
const id = router.currentRoute.value.params.id;
583-
await fetchGroup(Array.isArray(id) ? id[0] : id);
584-
setGetTransactionsFunction();
585-
});
586-
};
587-
588575
function setGetTransactionsFunction() {
589576
nextTransaction.setGetTransactionsFunction(async () => {
590577
const transactions = group.value?.groupItems.map(t => t.transaction);

front-end/src/renderer/pages/Transactions/components/History.vue

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ITransaction } from '@shared/interfaces';
33
import type { Transaction } from '@prisma/client';
44
55
import { computed, onBeforeMount, reactive, ref, watch } from 'vue';
6+
import { useRouter } from 'vue-router';
67
import { Prisma } from '@prisma/client';
78
89
import { Transaction as SDKTransaction } from '@hashgraph/sdk';
@@ -13,12 +14,10 @@ import { TRANSACTION_ACTION } from '@shared/constants';
1314
import useUserStore from '@renderer/stores/storeUser';
1415
import useNetworkStore from '@renderer/stores/storeNetwork';
1516
import useNotificationsStore from '@renderer/stores/storeNotifications';
16-
import useWebsocketConnection from '@renderer/stores/storeWebsocketConnection';
17+
import useNextTransactionStore from '@renderer/stores/storeNextTransaction';
1718
18-
import { useRouter } from 'vue-router';
19-
import useDisposableWs from '@renderer/composables/useDisposableWs';
2019
import useMarkNotifications from '@renderer/composables/useMarkNotifications';
21-
import useNextTransactionStore from '@renderer/stores/storeNextTransaction';
20+
import useWebsocketSubscription from '@renderer/composables/useWebsocketSubscription';
2221
2322
import { getTransactions, getTransactionsCount } from '@renderer/services/transactionService';
2423
import { getHistoryTransactions } from '@renderer/services/organization';
@@ -46,12 +45,11 @@ import TransactionId from '@renderer/components/ui/TransactionId.vue';
4645
const user = useUserStore();
4746
const network = useNetworkStore();
4847
const notifications = useNotificationsStore();
49-
const wsStore = useWebsocketConnection();
5048
const nextTransaction = useNextTransactionStore();
5149
5250
/* Composables */
5351
const router = useRouter();
54-
const ws = useDisposableWs();
52+
useWebsocketSubscription(TRANSACTION_ACTION, fetchTransactions);
5553
const { oldNotifications } = useMarkNotifications([
5654
NotificationType.TRANSACTION_INDICATOR_EXECUTED,
5755
NotificationType.TRANSACTION_INDICATOR_EXPIRED,
@@ -243,26 +241,13 @@ function setPreviousTransactionsIds(id: string | number) {
243241
}
244242
}
245243
246-
const subscribeToTransactionAction = () => {
247-
if (!user.selectedOrganization?.serverUrl) return;
248-
ws.on(user.selectedOrganization?.serverUrl, TRANSACTION_ACTION, async () => {
249-
await fetchTransactions();
250-
});
251-
};
252-
253244
/* Hooks */
254245
onBeforeMount(async () => {
255-
subscribeToTransactionAction();
256246
setGetTransactionsFunction();
257247
await fetchTransactions();
258248
});
259249
260250
/* Watchers */
261-
wsStore.$onAction(ctx => {
262-
if (ctx.name !== 'setup') return;
263-
ctx.after(() => subscribeToTransactionAction());
264-
});
265-
266251
watch([currentPage, pageSize, () => user.selectedOrganization, orgFilters], async () => {
267252
setGetTransactionsFunction();
268253
await fetchTransactions();

front-end/src/renderer/pages/Transactions/components/TransactionNodeRow.vue

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<script setup lang="ts">
2+
import type { INotificationReceiver } from '@shared/interfaces';
3+
24
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
35
import { useRouter } from 'vue-router';
6+
47
import useNotificationsStore from '@renderer/stores/storeNotifications.ts';
58
import useFilterNotifications from '@renderer/composables/useFilterNotifications.ts';
69
import { getTransactionTypeFromBackendType } from '@renderer/utils/sdk/transactions.ts';
@@ -23,6 +26,7 @@ const props = defineProps<{
2326
collection: TransactionNodeCollection;
2427
node: ITransactionNode;
2528
index: number;
29+
oldNotifications?: INotificationReceiver[];
2630
}>();
2731
2832
/* Emits */
@@ -44,33 +48,62 @@ const router = useRouter();
4448
const createTooltips = useCreateTooltips();
4549
4650
/* Computed */
47-
const hasNotifications = computed(() => {
48-
return notificationMonitor.filteredNotifications.value.length > 0;
49-
});
50-
const filteringNotificationType = computed(() => {
51-
let result: NotificationType | null;
51+
const filteringNotificationTypes = computed(() => {
52+
let result: NotificationType[];
5253
switch (props.collection) {
5354
case TransactionNodeCollection.READY_FOR_REVIEW:
54-
result = NotificationType.TRANSACTION_INDICATOR_APPROVE;
55+
result = [NotificationType.TRANSACTION_INDICATOR_APPROVE];
5556
break;
5657
case TransactionNodeCollection.READY_TO_SIGN:
57-
result = NotificationType.TRANSACTION_INDICATOR_SIGN;
58+
result = [NotificationType.TRANSACTION_INDICATOR_SIGN];
5859
break;
5960
case TransactionNodeCollection.READY_FOR_EXECUTION:
60-
result = NotificationType.TRANSACTION_INDICATOR_EXECUTABLE;
61+
result = [NotificationType.TRANSACTION_INDICATOR_EXECUTABLE];
6162
break;
62-
case TransactionNodeCollection.IN_PROGRESS:
6363
case TransactionNodeCollection.HISTORY:
64-
result = null;
64+
result = [
65+
NotificationType.TRANSACTION_INDICATOR_EXECUTED,
66+
NotificationType.TRANSACTION_INDICATOR_EXPIRED,
67+
NotificationType.TRANSACTION_INDICATOR_ARCHIVED,
68+
NotificationType.TRANSACTION_INDICATOR_CANCELLED,
69+
NotificationType.TRANSACTION_INDICATOR_FAILED,
70+
];
71+
break;
72+
case TransactionNodeCollection.IN_PROGRESS:
73+
result = [];
6574
break;
6675
}
6776
return result;
6877
});
78+
6979
const notificationMonitor = useFilterNotifications(
7080
computed(() => props.node),
71-
filteringNotificationType,
81+
filteringNotificationTypes,
7282
);
7383
84+
const hasOldNotifications = computed(() => {
85+
if (!props.oldNotifications || props.oldNotifications.length === 0) {
86+
return false;
87+
}
88+
89+
const notificationTypes = filteringNotificationTypes.value;
90+
91+
if (notificationTypes.length === 0) {
92+
return false;
93+
}
94+
95+
// Check if any old notifications match this node
96+
return props.oldNotifications.some(n => {
97+
const matchesType = notificationTypes.includes(n.notification.type);
98+
const matchesEntity = n.notification.entityId === (props.node.transactionId || props.node.groupId);
99+
return matchesType && matchesEntity;
100+
});
101+
});
102+
103+
const hasNotifications = computed(() => {
104+
return notificationMonitor.filteredNotifications.value.length > 0 || hasOldNotifications.value;
105+
});
106+
74107
const transactionType = computed(() => {
75108
let result: string;
76109
if (props.node.transactionType) {

0 commit comments

Comments
 (0)