Skip to content

Commit 16a1936

Browse files
committed
refactor deletedmessagegroups view data handling into a store
1 parent 6e051e2 commit 16a1936

11 files changed

+201
-236
lines changed

src/Frontend/src/components/failedmessages/DeletedMessageGroups.vue

Lines changed: 30 additions & 218 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,36 @@
11
<script setup lang="ts">
2-
import { onMounted, onUnmounted, ref } from "vue";
3-
import { useRoute, useRouter } from "vue-router";
2+
import { ref, watch } from "vue";
3+
import { useRouter } from "vue-router";
44
import { useShowToast } from "../../composables/toast";
5-
import createMessageGroupClient from "./messageGroupClient";
6-
import { useCookies } from "vue3-cookies";
75
import NoData from "../NoData.vue";
86
import TimeSince from "../TimeSince.vue";
97
import LicenseNotExpired from "../../components/LicenseNotExpired.vue";
108
import ServiceControlAvailable from "../ServiceControlAvailable.vue";
119
import ConfirmDialog from "../ConfirmDialog.vue";
1210
import routeLinks from "@/router/routeLinks";
13-
import FailureGroupView from "@/resources/FailureGroupView";
1411
import { TYPE } from "vue-toastification";
1512
import MetadataItem from "@/components/MetadataItem.vue";
1613
import ActionButton from "@/components/ActionButton.vue";
1714
import { faArrowRotateRight, faEnvelope } from "@fortawesome/free-solid-svg-icons";
1815
import { faClock } from "@fortawesome/free-regular-svg-icons";
19-
import { useServiceControlStore } from "@/stores/ServiceControlStore";
20-
21-
const statusesForRestoreOperation = ["restorestarted", "restoreprogressing", "restorefinalizing", "restorecompleted"] as const;
22-
type RestoreOperationStatus = (typeof statusesForRestoreOperation)[number];
23-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
24-
const otherStatuses = ["none", "working"] as const;
25-
type Status = RestoreOperationStatus | (typeof otherStatuses)[number];
26-
interface WorkflowState {
27-
status: Status;
28-
total?: number;
29-
failed?: boolean;
30-
message?: string;
31-
}
32-
33-
interface ExtendedFailureGroupView extends FailureGroupView {
34-
index: number;
35-
need_user_acknowledgement?: boolean;
36-
workflow_state: WorkflowState;
37-
operation_remaining_count?: number;
38-
hover2?: boolean;
39-
hover3?: boolean;
40-
operation_start_time?: string;
41-
last_operation_completion_time?: string;
42-
}
16+
import { useDeletedMessageGroupsStore, statusesForRestoreOperation, ExtendedFailureGroupView, Status } from "@/stores/DeletedMessageGroupsStore";
17+
import { useStoreAutoRefresh } from "@/composables/useAutoRefresh";
18+
import { storeToRefs } from "pinia";
4319
4420
let pollingFaster = false;
45-
const archiveGroups = ref<ExtendedFailureGroupView[]>([]);
46-
const undismissedRestoreGroups = ref<ExtendedFailureGroupView[]>([]);
47-
const loadingData = ref(true);
48-
const initialLoadComplete = ref(false);
49-
const emit = defineEmits<{
50-
InitialLoadComplete: [];
51-
}>();
52-
let refreshInterval: number | undefined = undefined;
53-
const route = useRoute();
21+
const { autoRefresh, isRefreshing, updateInterval } = useStoreAutoRefresh("deletedMessageGroups", useDeletedMessageGroupsStore, 5000);
22+
const { store } = autoRefresh();
23+
const { archiveGroups, classifiers, selectedClassifier } = storeToRefs(store);
5424
const router = useRouter();
5525
const showRestoreGroupModal = ref(false);
5626
const selectedGroup = ref<ExtendedFailureGroupView>();
57-
58-
const serviceControlStore = useServiceControlStore();
59-
const messageGroupClient = createMessageGroupClient();
60-
6127
const groupRestoreSuccessful = ref<boolean | null>(null);
62-
const selectedClassifier = ref<string | null>(null);
63-
const classifiers = ref<string[]>([]);
64-
65-
async function getGroupingClassifiers() {
66-
const [, data] = await serviceControlStore.fetchTypedFromServiceControl<string[]>("recoverability/classifiers");
67-
classifiers.value = data;
68-
}
69-
70-
function saveDefaultGroupingClassifier(classifier: string) {
71-
const cookies = useCookies().cookies;
72-
cookies.set("archived_groups_classification", classifier);
73-
}
7428
7529
async function classifierChanged(classifier: string) {
76-
saveDefaultGroupingClassifier(classifier);
77-
30+
store.setGrouping(classifier);
7831
selectedClassifier.value = classifier;
79-
archiveGroups.value = [];
80-
await loadArchivedMessageGroups(classifier);
81-
}
82-
83-
async function getArchiveGroups(classifier: string) {
84-
//get all deleted message groups
85-
const [, result] = await serviceControlStore.fetchTypedFromServiceControl<FailureGroupView[]>(`errors/groups/${classifier}`);
86-
87-
if (result.length === 0 && undismissedRestoreGroups.value.length > 0) {
88-
undismissedRestoreGroups.value.forEach((deletedGroup) => {
89-
deletedGroup.need_user_acknowledgement = true;
90-
deletedGroup.workflow_state.status = "restorecompleted";
91-
});
92-
}
9332
94-
undismissedRestoreGroups.value.forEach((deletedGroup) => {
95-
if (!result.find((group) => group.id === deletedGroup.id)) {
96-
deletedGroup.need_user_acknowledgement = true;
97-
deletedGroup.workflow_state.status = "restorecompleted";
98-
}
99-
});
100-
101-
// need a map in some ui state for controlling animations
102-
const mappedResults = result
103-
.filter((group) => !undismissedRestoreGroups.value.find((deletedGroup) => deletedGroup.id === group.id))
104-
.map(initializeGroupState)
105-
.concat(undismissedRestoreGroups.value);
106-
107-
let maxIndex = archiveGroups.value.reduce((currentMax, currentGroup) => Math.max(currentMax, currentGroup.index), 0);
108-
109-
mappedResults.forEach((serverGroup) => {
110-
const previousGroup = archiveGroups.value.find((oldGroup) => oldGroup.id === serverGroup.id);
111-
112-
if (previousGroup) {
113-
serverGroup.index = previousGroup.index;
114-
} else {
115-
serverGroup.index = ++maxIndex;
116-
}
117-
});
118-
119-
archiveGroups.value = mappedResults.sort((group1, group2) => {
120-
return group1.index - group2.index;
121-
});
122-
}
123-
124-
function initializeGroupState(group: FailureGroupView): ExtendedFailureGroupView {
125-
return {
126-
index: 0,
127-
workflow_state: createWorkflowState("none"),
128-
...group,
129-
};
130-
}
131-
132-
function loadDefaultGroupingClassifier() {
133-
const cookies = useCookies().cookies;
134-
const cookieGrouping = cookies.get("archived_groups_classification");
135-
136-
if (cookieGrouping) {
137-
return cookieGrouping;
138-
}
139-
140-
return null;
141-
}
142-
143-
async function loadArchivedMessageGroups(groupBy: string | null = null) {
144-
loadingData.value = true;
145-
if (!initialLoadComplete.value || !groupBy) {
146-
groupBy = loadDefaultGroupingClassifier();
147-
}
148-
149-
await getArchiveGroups(groupBy ?? (route.query.deletedGroupBy as string));
150-
loadingData.value = false;
151-
initialLoadComplete.value = true;
152-
153-
emit("InitialLoadComplete");
154-
}
155-
156-
//create workflow state
157-
function createWorkflowState(optionalStatus?: Status, optionalTotal?: number, optionalFailed?: boolean): WorkflowState {
158-
if (optionalTotal && optionalTotal <= 1) {
159-
optionalTotal = optionalTotal * 100;
160-
}
161-
162-
return {
163-
status: optionalStatus ?? "working",
164-
total: optionalTotal ?? 0,
165-
failed: optionalFailed ?? false,
166-
};
33+
await store.refresh();
16734
}
16835
16936
//Restore operation
@@ -176,18 +43,14 @@ function showRestoreGroupDialog(group: ExtendedFailureGroupView) {
17643
async function restoreGroup() {
17744
const group = selectedGroup.value;
17845
if (group) {
179-
// We're starting a restore, poll more frequently
180-
changeRefreshInterval(1000);
181-
undismissedRestoreGroups.value.push(group);
182-
183-
group.workflow_state = { status: "restorestarted", message: "Restore request initiated..." };
184-
group.operation_start_time = new Date().toUTCString();
185-
186-
const result = await messageGroupClient.restoreGroup(group.id);
187-
if (messageGroupClient.isError(result)) {
46+
const { result, errorMessage } = await store.restoreGroup(group);
47+
if (!result) {
18848
groupRestoreSuccessful.value = false;
189-
useShowToast(TYPE.ERROR, "Error", `Failed to restore the group: ${result.message}`);
49+
useShowToast(TYPE.ERROR, "Error", `Failed to restore the group: ${errorMessage}`);
19050
} else {
51+
// We're starting a restore, poll more frequently
52+
pollingFaster = true;
53+
updateInterval(1000);
19154
groupRestoreSuccessful.value = true;
19255
useShowToast(TYPE.INFO, "Info", "Group restore started...");
19356
}
@@ -211,20 +74,6 @@ function getClassesForRestoreOperation(stepStatus: Status, currentStatus: Status
21174
return getClasses(stepStatus, currentStatus, statusesForRestoreOperation);
21275
}
21376
214-
const acknowledgeGroup = function (dismissedGroup: FailureGroupView) {
215-
undismissedRestoreGroups.value.splice(
216-
undismissedRestoreGroups.value.findIndex((group) => {
217-
return group.id === dismissedGroup.id;
218-
}),
219-
1
220-
);
221-
222-
archiveGroups.value.splice(
223-
archiveGroups.value.findIndex((group) => group.id === dismissedGroup.id),
224-
1
225-
);
226-
};
227-
22877
function isBeingRestored(status: Status) {
22978
return (statusesForRestoreOperation as readonly Status[]).includes(status);
23079
}
@@ -237,44 +86,17 @@ function isRestoreInProgress() {
23786
return archiveGroups.value.some((group) => group.workflow_state.status !== "none" && group.workflow_state.status !== "restorecompleted");
23887
}
23988
240-
function changeRefreshInterval(milliseconds: number) {
241-
if (refreshInterval) {
242-
clearInterval(refreshInterval);
243-
}
244-
245-
refreshInterval = window.setInterval(() => {
246-
// If we're currently polling at 5 seconds and there is a restore in progress, then change the polling interval to poll every 1 second
247-
if (!pollingFaster && isRestoreInProgress()) {
248-
changeRefreshInterval(1000);
249-
pollingFaster = true;
250-
} else if (pollingFaster && !isRestoreInProgress()) {
251-
// if we're currently polling every 1 second and all restores are done, change polling frequency back to every 5 seconds
252-
changeRefreshInterval(5000);
253-
pollingFaster = false;
254-
}
255-
256-
loadArchivedMessageGroups();
257-
}, milliseconds);
258-
}
259-
260-
onUnmounted(() => {
261-
if (refreshInterval) {
262-
clearInterval(refreshInterval);
89+
watch(isRefreshing, () => {
90+
// If we're currently polling at 5 seconds and there is a restore in progress, then change the polling interval to poll every 1 second
91+
if (!pollingFaster && isRestoreInProgress()) {
92+
pollingFaster = true;
93+
updateInterval(1000);
94+
} else if (pollingFaster && !isRestoreInProgress()) {
95+
// if we're currently polling every 1 second and all restores are done, change polling frequency back to every 5 seconds
96+
pollingFaster = false;
97+
updateInterval(5000);
26398
}
26499
});
265-
266-
onMounted(async () => {
267-
await getGroupingClassifiers();
268-
let savedClassifier = loadDefaultGroupingClassifier();
269-
if (!savedClassifier) {
270-
savedClassifier = classifiers.value[0];
271-
}
272-
273-
selectedClassifier.value = savedClassifier;
274-
await loadArchivedMessageGroups();
275-
276-
changeRefreshInterval(5000);
277-
});
278100
</script>
279101

280102
<template>
@@ -307,31 +129,23 @@ onMounted(async () => {
307129
<div>
308130
<div class="row">
309131
<div class="col-sm-12">
310-
<no-data v-if="archiveGroups.length === 0 && !loadingData" title="message groups" message="There are currently no grouped message failures"></no-data>
132+
<no-data v-if="archiveGroups.length === 0 && !isRefreshing" title="message groups" message="There are currently no grouped message failures"></no-data>
311133
</div>
312134
</div>
313135

314136
<div class="row">
315137
<div class="col-sm-12 no-mobile-side-padding">
316138
<div v-if="archiveGroups.length > 0">
317-
<div
318-
:class="`row box box-group wf-${group.workflow_state.status} repeat-modify deleted-message-group`"
319-
v-for="(group, index) in archiveGroups"
320-
:key="index"
321-
:disabled="group.count == 0"
322-
@mouseenter="group.hover2 = true"
323-
@mouseleave="group.hover2 = false"
324-
@click.prevent="navigateToGroup(group.id)"
325-
>
139+
<div :class="`row box box-group wf-${group.workflow_state.status} repeat-modify deleted-message-group`" v-for="(group, index) in archiveGroups" :key="index" :disabled="group.count == 0" @click.prevent="navigateToGroup(group.id)">
326140
<div class="col-sm-12 no-mobile-side-padding">
327141
<div class="row">
328142
<div class="col-sm-12 no-side-padding">
329143
<div class="row box-header">
330144
<div class="col-sm-12 no-side-padding">
331-
<p class="lead break" v-bind:class="{ 'msg-type-hover': group.hover2, 'msg-type-hover-off': group.hover3 }">{{ group.title }}</p>
145+
<p class="lead break">{{ group.title }}</p>
332146
<p class="metadata" v-if="!isBeingRestored(group.workflow_state.status)">
333147
<MetadataItem :icon="faEnvelope">
334-
{{ group.count }} message<span v-if="group.count > 1">s</span>
148+
<span>{{ group.count }} message<span v-if="group.count > 1">s</span></span>
335149
<span v-if="group.operation_remaining_count"> (currently restoring {{ group.operation_remaining_count }} </span>
336150
</MetadataItem>
337151

@@ -357,8 +171,6 @@ onMounted(async () => {
357171
size="sm"
358172
:icon="faArrowRotateRight"
359173
:disabled="group.count === 0 || isBeingRestored(group.workflow_state.status)"
360-
@mouseenter="group.hover3 = true"
361-
@mouseleave="group.hover3 = false"
362174
v-if="archiveGroups.length > 0"
363175
@click.stop="showRestoreGroupDialog(group)"
364176
>
@@ -378,7 +190,7 @@ onMounted(async () => {
378190
</li>
379191
<li v-if="group.workflow_state.status === 'restorecompleted'">
380192
<div class="retry-completed bulk-retry-progress-status">Restore request completed</div>
381-
<button type="button" class="btn btn-default btn-primary btn-xs btn-retry-dismiss" v-if="group.need_user_acknowledgement == true" @click.stop="acknowledgeGroup(group)">Dismiss</button>
193+
<button type="button" class="btn btn-default btn-primary btn-xs btn-retry-dismiss" v-if="group.need_user_acknowledgement == true" @click.stop="store.acknowledgeGroup(group)">Dismiss</button>
382194
</li>
383195
</ul>
384196
<div class="op-metadata">

src/Frontend/src/components/failedmessages/messageGroupClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ export interface ErrorResponse {
77
message: string;
88
}
99

10-
class MessageGroupClient {
10+
export class MessageGroupClient {
1111
serviceControlStore: ServiceControlStore;
12-
constructor() {
12+
constructor(store?: ServiceControlStore) {
1313
//this module is only called from within view setup or other pinia stores, so this call is lifecycle safe
14-
this.serviceControlStore = useServiceControlStore();
14+
this.serviceControlStore = store ?? useServiceControlStore();
1515
}
1616

1717
public async getExceptionGroups(classifier: string = "") {

src/Frontend/src/composables/autoRefresh.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,10 @@ export default function useFetchWithAutoRefresh(name: string, fetch: () => Promi
5959
};
6060

6161
const updateInterval = (newIntervalMs: number) => {
62+
if (interval.value === newIntervalMs) return;
63+
6264
interval.value = newIntervalMs;
65+
console.debug(`updated polling ${name} to ${newIntervalMs}ms`);
6366
pause();
6467
if (newIntervalMs > 0) {
6568
resume();

0 commit comments

Comments
 (0)