diff --git a/client/src/components/Grid/configs/histories.ts b/client/src/components/Grid/configs/histories.ts index 0186d3851920..189b27c1cc9d 100644 --- a/client/src/components/Grid/configs/histories.ts +++ b/client/src/components/Grid/configs/histories.ts @@ -1,4 +1,5 @@ import { + faBurn, faExchangeAlt, faEye, faPlus, @@ -10,7 +11,7 @@ import { } from "@fortawesome/free-solid-svg-icons"; import { useEventBus } from "@vueuse/core"; -import { deleteHistories, deleteHistory, historiesFetcher, undeleteHistories, undeleteHistory } from "@/api/histories"; +import { historiesFetcher } from "@/api/histories"; import { updateTags } from "@/api/tags"; import { useHistoryStore } from "@/stores/historyStore"; import Filtering, { contains, equals, expandNameTag, toBool, type ValidFilter } from "@/utils/filtering"; @@ -70,9 +71,8 @@ const batch: BatchOperationArray = [ if (confirm(_l(`Are you sure that you want to delete the selected histories?`))) { try { const historyIds = data.map((x) => String(x.id)); - await deleteHistories({ ids: historyIds }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(data.length, true); + await historyStore.deleteHistories(historyIds); return { status: "success", message: `Deleted ${data.length} histories.`, @@ -94,9 +94,8 @@ const batch: BatchOperationArray = [ if (confirm(_l(`Are you sure that you want to restore the selected histories?`))) { try { const historyIds = data.map((x) => String(x.id)); - await undeleteHistories({ ids: historyIds }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(data.length); + await historyStore.restoreHistories(historyIds); return { status: "success", message: `Restored ${data.length} histories.`, @@ -112,15 +111,14 @@ const batch: BatchOperationArray = [ }, { title: "Purge", - icon: faTrash, + icon: faBurn, condition: (data: Array) => !data.some((x) => x.purged), handler: async (data: Array) => { if (confirm(_l(`Are you sure that you want to permanently delete the selected histories?`))) { try { const historyIds = data.map((x) => String(x.id)); - await deleteHistories({ ids: historyIds, purge: true }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(data.length, true); + await historyStore.deleteHistories(historyIds, true); return { status: "success", message: `Purged ${data.length} histories.`, @@ -191,9 +189,8 @@ const fields: FieldArray = [ handler: async (data: HistoryEntry) => { if (confirm(_l("Are you sure that you want to delete this history?"))) { try { - await deleteHistory({ history_id: String(data.id) }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(1, true); + await historyStore.deleteHistory(String(data.id)); return { status: "success", message: `'${data.name}' has been deleted.`, @@ -209,14 +206,13 @@ const fields: FieldArray = [ }, { title: "Delete Permanently", - icon: faTrash, + icon: faBurn, condition: (data: HistoryEntry) => !data.purged, handler: async (data: HistoryEntry) => { if (confirm(_l("Are you sure that you want to permanently delete this history?"))) { try { - await deleteHistory({ history_id: String(data.id), purge: true }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(1, true); + await historyStore.deleteHistory(String(data.id), true); return { status: "success", message: `'${data.name}' has been permanently deleted.`, @@ -236,9 +232,8 @@ const fields: FieldArray = [ condition: (data: HistoryEntry) => !!data.deleted && !data.purged, handler: async (data: HistoryEntry) => { try { - await undeleteHistory({ history_id: String(data.id) }); const historyStore = useHistoryStore(); - await historyStore.handleTotalCountChange(1); + await historyStore.restoreHistory(String(data.id)); return { status: "success", message: `'${data.name}' has been restored.`, diff --git a/client/src/stores/historyStore.ts b/client/src/stores/historyStore.ts index 4174cdd3b98c..f6e9f7e11962 100644 --- a/client/src/stores/historyStore.ts +++ b/client/src/stores/historyStore.ts @@ -8,14 +8,19 @@ import { type HistorySummary, type HistorySummaryExtended, } from "@/api"; -import { historyFetcher } from "@/api/histories"; +import { + deleteHistories as deleteHistoriesByIds, + deleteHistory as deleteHistoryById, + historyFetcher, + undeleteHistories, + undeleteHistory, +} from "@/api/histories"; import { archiveHistory, unarchiveHistory } from "@/api/histories.archived"; import { HistoryFilters } from "@/components/History/HistoryFilters"; import { useUserLocalStorage } from "@/composables/userLocalStorage"; import { cloneHistory, createAndSelectNewHistory, - deleteHistoryById, getCurrentHistoryFromServer, getHistoryByIdFromServer, getHistoryCount, @@ -190,16 +195,67 @@ export const useHistoryStore = defineStore("historyStore", () => { return filteredHistoryIds[0]; } - async function deleteHistory(historyId: string, purge: boolean) { - const deletedHistory = (await deleteHistoryById(historyId, purge)) as HistorySummary; - const nextAvailableHistoryId = getNextAvailableHistoryId([deletedHistory.id]); - if (nextAvailableHistoryId) { - await setCurrentHistory(nextAvailableHistoryId); - } else { - await createNewHistory(); + async function deleteHistory(historyId: string, purge = false) { + try { + const { data } = await deleteHistoryById({ history_id: historyId, purge }); + const deletedHistory = data as AnyHistory; + if (currentHistoryId.value === historyId) { + const nextAvailableHistoryId = getNextAvailableHistoryId([deletedHistory.id]); + if (nextAvailableHistoryId) { + await setCurrentHistory(nextAvailableHistoryId); + } else { + await createNewHistory(); + } + } + del(storedHistories.value, deletedHistory.id); + await handleTotalCountChange(1, true); + } catch (error) { + rethrowSimple(error); + } + } + + async function deleteHistories(ids: string[], purge = false) { + try { + const { data } = await deleteHistoriesByIds({ ids, purge }); + const deletedHistories = data as AnyHistory[]; + const historyIds = deletedHistories.map((x) => String(x.id)); + if (currentHistoryId.value && historyIds.includes(currentHistoryId.value)) { + const nextAvailableHistoryId = getNextAvailableHistoryId(historyIds); + if (nextAvailableHistoryId) { + await setCurrentHistory(nextAvailableHistoryId); + } else { + await createNewHistory(); + } + } + deletedHistories.forEach((history) => { + del(storedHistories.value, history.id); + }); + await handleTotalCountChange(deletedHistories.length, true); + } catch (error) { + rethrowSimple(error); + } + } + + async function restoreHistory(historyId: string) { + try { + const { data } = await undeleteHistory({ history_id: historyId }); + const restoredHistory = data as AnyHistory; + await handleTotalCountChange(1); + setHistory(restoredHistory); + } catch (error) { + rethrowSimple(error); + } + } + + async function restoreHistories(ids: string[]) { + try { + const { data } = await undeleteHistories({ ids }); + const restoredHistories = data as AnyHistory[]; + await handleTotalCountChange(restoredHistories.length); + setHistories(restoredHistories); + } catch (error) { + rethrowSimple(error); } - del(storedHistories.value, deletedHistory.id); - await handleTotalCountChange(1, true); } async function loadCurrentHistory(since?: string): Promise { @@ -348,6 +404,9 @@ export const useHistoryStore = defineStore("historyStore", () => { copyHistory, createNewHistory, deleteHistory, + deleteHistories, + restoreHistory, + restoreHistories, handleTotalCountChange, loadCurrentHistory, loadHistories, diff --git a/client/src/stores/services/history.services.js b/client/src/stores/services/history.services.ts similarity index 60% rename from client/src/stores/services/history.services.js rename to client/src/stores/services/history.services.ts index 6f095015d0ae..2907e4f66506 100644 --- a/client/src/stores/services/history.services.js +++ b/client/src/stores/services/history.services.ts @@ -1,14 +1,17 @@ -import axios from "axios"; -import { prependPath } from "utils/redirect"; +import axios, { type AxiosResponse } from "axios"; +import type { ApiResponse } from "openapi-typescript-fetch"; + +import type { AnyHistory } from "@/api"; +import { prependPath } from "@/utils/redirect"; /** * Generic json getter - * @param {*} response - * @return {Object} response.data or throws an error if response status is not 200 + * @param response + * @return response.data or throws an error if response status is not 200 */ -function doResponse(response) { +function doResponse(response: AxiosResponse | ApiResponse) { if (response.status !== 200) { - throw new Error(response); + throw new Error(response.statusText); } return response.data; } @@ -16,18 +19,15 @@ function doResponse(response) { /** * Some current endpoints don't accept JSON, so we need to * do some massaging to send in old form post data. - * @param {Object} fields */ -function formData(fields = {}) { +function formData(fields = {} as Record) { return Object.keys(fields).reduce((result, fieldName) => { result.set(fieldName, fields[fieldName]); return result; }, new FormData()); } -/** - * Default history request parameters. - */ +/** Default history request parameters. */ const stdHistoryParams = { view: "summary", }; @@ -43,7 +43,7 @@ const extendedHistoryParams = { /** * Create a new history, select it as the current history, and return it if successful. - * @return {Object} the new history or throws an error if new history creation fails + * @return the new history or throws an error if new history creation fails */ export async function createAndSelectNewHistory() { const url = "history/create_new_current"; @@ -57,11 +57,11 @@ export async function createAndSelectNewHistory() { /** * Generates copy of history on server. - * @param {Object} history Source history - * @param {String} name New history name - * @param {Boolean} copyAll Copy existing contents + * @param history Source history + * @param name New history name + * @param copyAll Copy existing contents */ -export async function cloneHistory(history, name, copyAll) { +export async function cloneHistory(history: AnyHistory, name: string, copyAll: boolean) { const url = "api/histories"; const payload = { name, @@ -73,23 +73,12 @@ export async function cloneHistory(history, name, copyAll) { return doResponse(response); } -/** - * Delete history on server and return the deleted history. - * @param {String} id Encoded history id - * @param {Boolean} [purge=false] Permanent delete - */ -export async function deleteHistoryById(id, purge = false) { - const url = `api/histories/${id}` + (purge ? "?purge=True" : ""); - const response = await axios.delete(prependPath(url), { params: stdHistoryParams }); - return doResponse(response); -} - /** * Get current history from server and return it. - * @param {String} since timestamp to get histories since - * @return {Object} the current history + * @param since timestamp to get histories since + * @return the current history */ -export async function getCurrentHistoryFromServer(since = undefined) { +export async function getCurrentHistoryFromServer(since: string | undefined = undefined) { const url = "history/current_history_json"; const response = await axios.get(prependPath(url), { params: { since: since } }); return doResponse(response); @@ -97,10 +86,10 @@ export async function getCurrentHistoryFromServer(since = undefined) { /** * Set current history on server and return it. - * @param {String} historyId Encoded history id - * @return {Object} the current history + * @param historyId Encoded history id + * @return the current history */ -export async function setCurrentHistoryOnServer(historyId) { +export async function setCurrentHistoryOnServer(historyId: string) { const url = "history/set_as_current"; const response = await axios.get(prependPath(url), { params: { id: historyId } }); return doResponse(response); @@ -108,12 +97,12 @@ export async function setCurrentHistoryOnServer(historyId) { /** * Get list of histories from server and return them. - * @param {Number} offset to start from (default = 0) - * @param {Number | null} limit of histories to load (default = null; in which case no limit) - * @param {String} queryString to append to url in the form `q=filter&qv=val&q=...` - * @return {Promise.} list of histories + * @param offset to start from (default = 0) + * @param limit of histories to load (default = null; in which case no limit) + * @param queryString to append to url in the form `q=filter&qv=val&q=...` + * @return list of histories */ -export async function getHistoryList(offset = 0, limit = null, queryString = "") { +export async function getHistoryList(offset = 0, limit: number | null = null, queryString = "") { const params = `view=summary&order=update_time&offset=${offset}`; let url = `api/histories?${params}`; if (limit !== null) { @@ -128,7 +117,7 @@ export async function getHistoryList(offset = 0, limit = null, queryString = "") /** * Get number of histories for current user from server and return them. - * @return {Promise.} number of histories + * @return number of histories */ export async function getHistoryCount() { // This url is temp. for this PR, waiting on: @@ -138,11 +127,8 @@ export async function getHistoryCount() { return doResponse(response); } -/** - * Load one history by id - * @param {String} id - */ -export async function getHistoryByIdFromServer(id) { +/** Load one history by id */ +export async function getHistoryByIdFromServer(id: string) { const path = `api/histories/${id}`; const response = await axios.get(prependPath(path), { params: extendedHistoryParams }); return doResponse(response); @@ -151,10 +137,10 @@ export async function getHistoryByIdFromServer(id) { /** * Set permissions to private for indicated history * TODO: rewrite API endpoint for this - * @param {Object} history the history to secure - * @return {Object} the secured history + * @param history the history to secure + * @return the secured history */ -export async function secureHistoryOnServer(history) { +export async function secureHistoryOnServer(history: AnyHistory) { const { id } = history; const url = "history/make_private"; const response = await axios.post(prependPath(url), formData({ history_id: id })); @@ -166,11 +152,11 @@ export async function secureHistoryOnServer(history) { /** * Update specific fields in history - * @param {Object} historyId the history id to update - * @param {Object} payload fields to update - * @return {Object} the updated history + * @param historyId the history id to update + * @param payload fields to update + * @return the updated history */ -export async function updateHistoryFields(historyId, payload) { +export async function updateHistoryFields(historyId: string, payload: Record) { const url = `api/histories/${historyId}`; const response = await axios.put(prependPath(url), payload, { params: extendedHistoryParams }); return doResponse(response);