From 64e218a9f3376a0489d7ccf8d4b97e2b2ab57881 Mon Sep 17 00:00:00 2001
From: filtered <176114999+webfiltered@users.noreply.github.com>
Date: Wed, 22 Jan 2025 07:24:59 +1100
Subject: [PATCH 1/2] [Refactor] Task execution into task runner class
---
src/components/maintenance/TaskCard.vue | 14 +--
src/components/maintenance/TaskListItem.vue | 14 +--
src/stores/maintenanceTaskStore.ts | 97 ++++++++++++---------
src/types/desktop/maintenanceTypes.ts | 12 ---
src/views/MaintenanceView.vue | 2 +-
5 files changed, 70 insertions(+), 69 deletions(-)
diff --git a/src/components/maintenance/TaskCard.vue b/src/components/maintenance/TaskCard.vue
index fc466c68e..6fdd16808 100644
--- a/src/components/maintenance/TaskCard.vue
+++ b/src/components/maintenance/TaskCard.vue
@@ -5,12 +5,12 @@
>
@@ -38,7 +38,7 @@
@@ -54,7 +54,7 @@ import type { MaintenanceTask } from '@/types/desktop/maintenanceTypes'
import { useMinLoadingDurationRef } from '@/utils/refUtil'
const taskStore = useMaintenanceTaskStore()
-const state = computed(() => taskStore.getState(props.task))
+const runner = computed(() => taskStore.getRunner(props.task))
// Properties
const props = defineProps<{
@@ -68,14 +68,14 @@ defineEmits<{
// Bindings
const description = computed(() =>
- state.value.state === 'error'
+ runner.value.state === 'error'
? props.task.errorDescription ?? props.task.shortDescription
: props.task.shortDescription
)
// Use a minimum run time to ensure tasks "feel" like they have run
-const reactiveLoading = computed(() => state.value.refreshing)
-const reactiveExecuting = computed(() => state.value.executing)
+const reactiveLoading = computed(() => runner.value.refreshing)
+const reactiveExecuting = computed(() => runner.value.executing)
const isLoading = useMinLoadingDurationRef(reactiveLoading, 250)
const isExecuting = useMinLoadingDurationRef(reactiveExecuting, 250)
diff --git a/src/components/maintenance/TaskListItem.vue b/src/components/maintenance/TaskListItem.vue
index 8f25ef45b..8b9c84d7b 100644
--- a/src/components/maintenance/TaskListItem.vue
+++ b/src/components/maintenance/TaskListItem.vue
@@ -2,12 +2,12 @@
-
+
|
{{ task.name }}
@@ -51,7 +51,7 @@ import { useMinLoadingDurationRef } from '@/utils/refUtil'
import TaskListStatusIcon from './TaskListStatusIcon.vue'
const taskStore = useMaintenanceTaskStore()
-const state = computed(() => taskStore.getState(props.task))
+const runner = computed(() => taskStore.getRunner(props.task))
// Properties
const props = defineProps<{
@@ -65,14 +65,14 @@ defineEmits<{
// Binding
const severity = computed(() =>
- state.value.state === 'error' || state.value.state === 'warning'
+ runner.value.state === 'error' || runner.value.state === 'warning'
? 'primary'
: 'secondary'
)
// Use a minimum run time to ensure tasks "feel" like they have run
-const reactiveLoading = computed(() => state.value.refreshing)
-const reactiveExecuting = computed(() => state.value.executing)
+const reactiveLoading = computed(() => runner.value.refreshing)
+const reactiveExecuting = computed(() => runner.value.executing)
const isLoading = useMinLoadingDurationRef(reactiveLoading, 250)
const isExecuting = useMinLoadingDurationRef(reactiveExecuting, 250)
diff --git a/src/stores/maintenanceTaskStore.ts b/src/stores/maintenanceTaskStore.ts
index 68bb7e93a..bd5704112 100644
--- a/src/stores/maintenanceTaskStore.ts
+++ b/src/stores/maintenanceTaskStore.ts
@@ -3,12 +3,40 @@ import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { DESKTOP_MAINTENANCE_TASKS } from '@/constants/desktopMaintenanceTasks'
-import type {
- MaintenanceTask,
- MaintenanceTaskState
-} from '@/types/desktop/maintenanceTypes'
+import type { MaintenanceTask } from '@/types/desktop/maintenanceTypes'
import { electronAPI } from '@/utils/envUtil'
+type MaintenanceTaskState = 'warning' | 'error' | 'resolved' | 'OK' | 'skipped'
+
+/** State of a maintenance task, managed by the maintenance task store. */
+export class MaintenanceTaskRunner {
+ /** The current state of the task. */
+ state?: 'warning' | 'error' | 'resolved' | 'OK' | 'skipped'
+ /** Whether the task state is currently being refreshed. */
+ refreshing?: boolean
+ /** Whether the task is currently running. */
+ executing?: boolean
+ /** The error message that occurred when the task failed. */
+ error?: string
+
+ /** Wraps the execution of a maintenance task, updating state and rethrowing errors. */
+ async execute(task: MaintenanceTask) {
+ try {
+ this.executing = true
+ const success = await task.execute()
+ if (!success) return false
+
+ this.error = undefined
+ return true
+ } catch (error) {
+ this.error = (error as Error)?.message
+ throw error
+ } finally {
+ this.executing = false
+ }
+ }
+}
+
/**
* User-initiated maintenance tasks. Currently only used by the desktop app maintenance view.
*
@@ -24,53 +52,34 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
const isRunningTerminalCommand = computed(() =>
tasks.value
.filter((task) => task.usesTerminal)
- .some((task) => getState(task)?.executing)
+ .some((task) => getRunner(task)?.executing)
)
const isRunningInstallationFix = computed(() =>
tasks.value
.filter((task) => task.isInstallationFix)
- .some((task) => getState(task)?.executing)
+ .some((task) => getRunner(task)?.executing)
)
// Task list
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
const taskStates = ref(
- new Map(
- DESKTOP_MAINTENANCE_TASKS.map((x) => [x.id, {}])
+ new Map(
+ DESKTOP_MAINTENANCE_TASKS.map((x) => [x.id, new MaintenanceTaskRunner()])
)
)
/** True if any tasks are in an error state. */
const anyErrors = computed(() =>
- tasks.value.some((task) => getState(task).state === 'error')
+ tasks.value.some((task) => getRunner(task).state === 'error')
)
- /** Wraps the execution of a maintenance task, updating state and rethrowing errors. */
- const execute = async (task: MaintenanceTask) => {
- const state = getState(task)
-
- try {
- state.executing = true
- const success = await task.execute()
- if (!success) return false
-
- state.error = undefined
- return true
- } catch (error) {
- state.error = (error as Error)?.message
- throw error
- } finally {
- state.executing = false
- }
- }
-
/**
* Returns the matching state object for a task.
* @param task Task to get the matching state object for
* @returns The state object for this task
*/
- const getState = (task: MaintenanceTask) => taskStates.value.get(task.id)!
+ const getRunner = (task: MaintenanceTask) => taskStates.value.get(task.id)!
/**
* Updates the task list with the latest validation state.
@@ -87,15 +96,15 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
// Update each task state
for (const task of tasks.value) {
- const state = getState(task)
+ const runner = getRunner(task)
- state.refreshing = update[task.id] === undefined
+ runner.refreshing = update[task.id] === undefined
// Mark resolved
- if (state.state === 'error' && update[task.id] === 'OK')
- state.state = 'resolved'
- if (update[task.id] === 'OK' && state.state === 'resolved') continue
+ if (runner.state === 'error' && update[task.id] === 'OK')
+ runner.state = 'resolved'
+ if (update[task.id] === 'OK' && runner.state === 'resolved') continue
- if (update[task.id]) state.state = update[task.id]
+ if (update[task.id]) runner.state = update[task.id]
}
// Final update
@@ -103,11 +112,11 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
isRefreshing.value = false
for (const task of tasks.value) {
- const state = getState(task)
- state.refreshing = false
- if (state.state === 'resolved') continue
+ const runner = getRunner(task)
+ runner.refreshing = false
+ if (runner.state === 'resolved') continue
- state.state = update[task.id] ?? 'skipped'
+ runner.state = update[task.id] ?? 'skipped'
}
}
}
@@ -115,8 +124,8 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
/** Clears the resolved status of tasks (when changing filters) */
const clearResolved = () => {
for (const task of tasks.value) {
- const state = getState(task)
- if (state?.state === 'resolved') state.state = 'OK'
+ const runner = getRunner(task)
+ if (runner?.state === 'resolved') runner.state = 'OK'
}
}
@@ -127,13 +136,17 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
await electron.Validation.validateInstallation(processUpdate)
}
+ const execute = async (task: MaintenanceTask) => {
+ return getRunner(task).execute(task)
+ }
+
return {
tasks,
isRefreshing,
isRunningTerminalCommand,
isRunningInstallationFix,
execute,
- getState,
+ getRunner,
processUpdate,
clearResolved,
/** True if any tasks are in an error state. */
diff --git a/src/types/desktop/maintenanceTypes.ts b/src/types/desktop/maintenanceTypes.ts
index 46965bb14..d353b51d4 100644
--- a/src/types/desktop/maintenanceTypes.ts
+++ b/src/types/desktop/maintenanceTypes.ts
@@ -39,18 +39,6 @@ export interface MaintenanceTask {
isInstallationFix?: boolean
}
-/** State of a maintenance task, managed by the maintenance task store. */
-export interface MaintenanceTaskState {
- /** The current state of the task. */
- state?: 'warning' | 'error' | 'resolved' | 'OK' | 'skipped'
- /** Whether the task state is currently being refreshed. */
- refreshing?: boolean
- /** Whether the task is currently running. */
- executing?: boolean
- /** The error message that occurred when the task failed. */
- error?: string
-}
-
/** The filter options for the maintenance task list. */
export interface MaintenanceFilter {
/** CSS classes used for the filter button icon, e.g. 'pi pi-cross' */
diff --git a/src/views/MaintenanceView.vue b/src/views/MaintenanceView.vue
index 41bc49bd9..f89bc7cbd 100644
--- a/src/views/MaintenanceView.vue
+++ b/src/views/MaintenanceView.vue
@@ -125,7 +125,7 @@ const displayAsList = ref(PrimeIcons.TH_LARGE)
const errorFilter = computed(() =>
taskStore.tasks.filter((x) => {
- const { state } = taskStore.getState(x)
+ const { state } = taskStore.getRunner(x)
return state === 'error' || state === 'resolved'
})
)
From a7a5e3cf67542d4eee9884676b91753ad08ab590 Mon Sep 17 00:00:00 2001
From: filtered <176114999+webfiltered@users.noreply.github.com>
Date: Wed, 22 Jan 2025 08:15:46 +1100
Subject: [PATCH 2/2] [Refactor] Task state updates to TaskRunner
---
src/components/maintenance/TaskListItem.vue | 4 +-
src/stores/maintenanceTaskStore.ts | 69 +++++++++++++--------
src/views/MaintenanceView.vue | 4 +-
3 files changed, 48 insertions(+), 29 deletions(-)
diff --git a/src/components/maintenance/TaskListItem.vue b/src/components/maintenance/TaskListItem.vue
index 8b9c84d7b..c3494dd1e 100644
--- a/src/components/maintenance/TaskListItem.vue
+++ b/src/components/maintenance/TaskListItem.vue
@@ -2,8 +2,8 @@
diff --git a/src/stores/maintenanceTaskStore.ts b/src/stores/maintenanceTaskStore.ts
index bd5704112..021d5f878 100644
--- a/src/stores/maintenanceTaskStore.ts
+++ b/src/stores/maintenanceTaskStore.ts
@@ -6,12 +6,37 @@ import { DESKTOP_MAINTENANCE_TASKS } from '@/constants/desktopMaintenanceTasks'
import type { MaintenanceTask } from '@/types/desktop/maintenanceTypes'
import { electronAPI } from '@/utils/envUtil'
-type MaintenanceTaskState = 'warning' | 'error' | 'resolved' | 'OK' | 'skipped'
+/** State of a maintenance task, managed by the maintenance task store. */
+type MaintenanceTaskState = 'warning' | 'error' | 'OK' | 'skipped'
+
+// Type not exported by API
+type ValidationState = InstallValidation['basePath']
+// Add index to API type
+type IndexedUpdate = InstallValidation & Record
/** State of a maintenance task, managed by the maintenance task store. */
export class MaintenanceTaskRunner {
- /** The current state of the task. */
- state?: 'warning' | 'error' | 'resolved' | 'OK' | 'skipped'
+ constructor(readonly task: MaintenanceTask) {}
+
+ private _state?: MaintenanceTaskState
+ /** The current state of the task. Setter also controls {@link resolved} as a side-effect. */
+ get state() {
+ return this._state
+ }
+
+ /** Updates the task state and {@link resolved} status. */
+ setState(value: MaintenanceTaskState) {
+ // Mark resolved
+ if (this._state === 'error' && value === 'OK') this.resolved = true
+ // Mark unresolved (if previously resolved)
+ if (value === 'error') this.resolved &&= false
+
+ this._state = value
+ }
+
+ /** `true` if the task has been resolved (was `error`, now `OK`). This is a side-effect of the {@link state} setter. */
+ resolved?: boolean
+
/** Whether the task state is currently being refreshed. */
refreshing?: boolean
/** Whether the task is currently running. */
@@ -19,6 +44,18 @@ export class MaintenanceTaskRunner {
/** The error message that occurred when the task failed. */
error?: string
+ update(update: IndexedUpdate) {
+ const state = update[this.task.id]
+
+ this.refreshing = state === undefined
+ if (state) this.setState(state)
+ }
+
+ finaliseUpdate(update: IndexedUpdate) {
+ this.refreshing = false
+ this.setState(update[this.task.id] ?? 'skipped')
+ }
+
/** Wraps the execution of a maintenance task, updating state and rethrowing errors. */
async execute(task: MaintenanceTask) {
try {
@@ -65,7 +102,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
const taskStates = ref(
new Map(
- DESKTOP_MAINTENANCE_TASKS.map((x) => [x.id, new MaintenanceTaskRunner()])
+ DESKTOP_MAINTENANCE_TASKS.map((x) => [x.id, new MaintenanceTaskRunner(x)])
)
)
@@ -86,25 +123,12 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
* @param validationUpdate Update details passed in by electron
*/
const processUpdate = (validationUpdate: InstallValidation) => {
- // Type not exported by API
- type ValidationState = InstallValidation['basePath']
- // Add index to API type
- type IndexedUpdate = InstallValidation & Record
-
const update = validationUpdate as IndexedUpdate
isRefreshing.value = true
// Update each task state
for (const task of tasks.value) {
- const runner = getRunner(task)
-
- runner.refreshing = update[task.id] === undefined
- // Mark resolved
- if (runner.state === 'error' && update[task.id] === 'OK')
- runner.state = 'resolved'
- if (update[task.id] === 'OK' && runner.state === 'resolved') continue
-
- if (update[task.id]) runner.state = update[task.id]
+ getRunner(task).update(update)
}
// Final update
@@ -112,11 +136,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
isRefreshing.value = false
for (const task of tasks.value) {
- const runner = getRunner(task)
- runner.refreshing = false
- if (runner.state === 'resolved') continue
-
- runner.state = update[task.id] ?? 'skipped'
+ getRunner(task).finaliseUpdate(update)
}
}
}
@@ -124,8 +144,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
/** Clears the resolved status of tasks (when changing filters) */
const clearResolved = () => {
for (const task of tasks.value) {
- const runner = getRunner(task)
- if (runner?.state === 'resolved') runner.state = 'OK'
+ getRunner(task).resolved &&= false
}
}
diff --git a/src/views/MaintenanceView.vue b/src/views/MaintenanceView.vue
index f89bc7cbd..0d709caf3 100644
--- a/src/views/MaintenanceView.vue
+++ b/src/views/MaintenanceView.vue
@@ -125,8 +125,8 @@ const displayAsList = ref(PrimeIcons.TH_LARGE)
const errorFilter = computed(() =>
taskStore.tasks.filter((x) => {
- const { state } = taskStore.getRunner(x)
- return state === 'error' || state === 'resolved'
+ const { state, resolved } = taskStore.getRunner(x)
+ return state === 'error' || resolved
})
)
| |