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" ;
44import { useShowToast } from " ../../composables/toast" ;
5- import createMessageGroupClient from " ./messageGroupClient" ;
6- import { useCookies } from " vue3-cookies" ;
75import NoData from " ../NoData.vue" ;
86import TimeSince from " ../TimeSince.vue" ;
97import LicenseNotExpired from " ../../components/LicenseNotExpired.vue" ;
108import ServiceControlAvailable from " ../ServiceControlAvailable.vue" ;
119import ConfirmDialog from " ../ConfirmDialog.vue" ;
1210import routeLinks from " @/router/routeLinks" ;
13- import FailureGroupView from " @/resources/FailureGroupView" ;
1411import { TYPE } from " vue-toastification" ;
1512import MetadataItem from " @/components/MetadataItem.vue" ;
1613import ActionButton from " @/components/ActionButton.vue" ;
1714import { faArrowRotateRight , faEnvelope } from " @fortawesome/free-solid-svg-icons" ;
1815import { 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
4420let 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 );
5424const router = useRouter ();
5525const showRestoreGroupModal = ref (false );
5626const selectedGroup = ref <ExtendedFailureGroupView >();
57-
58- const serviceControlStore = useServiceControlStore ();
59- const messageGroupClient = createMessageGroupClient ();
60-
6127const 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
7529async 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) {
17643async 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-
22877function 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" >
0 commit comments