From 78c00a28f309f6931b6ad5360715cef7a6677054 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Mon, 25 Mar 2024 12:38:08 +0800 Subject: [PATCH] feat: add retry mechanism for saving posts on the UC end (#5578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area ui /kind improvement /milestone 2.14.x #### What this PR does / why we need it: 为 UC 端保存文章的操作添加重试机制,防止出现因为锁导致的保存失败问题。 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/5474 #### Special notes for your reviewer: 需要多次尝试编辑文章,以及设置表单的保存功能。 #### Does this PR introduce a user-facing change? ```release-note 为 UC 端保存文章的操作添加重试机制,防止出现因为锁导致的保存失败问题。 ``` --- .../modules/contents/posts/PostEditor.vue | 26 ++++--- .../posts/components/PostCreationModal.vue | 2 +- .../posts/components/PostSettingEditModal.vue | 69 ++++++++----------- .../composables/use-post-update-mutate.ts | 38 ++++++++++ 4 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 ui/uc-src/modules/contents/posts/composables/use-post-update-mutate.ts diff --git a/ui/uc-src/modules/contents/posts/PostEditor.vue b/ui/uc-src/modules/contents/posts/PostEditor.vue index f0aabe9a3..47c5dd10a 100644 --- a/ui/uc-src/modules/contents/posts/PostEditor.vue +++ b/ui/uc-src/modules/contents/posts/PostEditor.vue @@ -13,30 +13,27 @@ import { VSpace, } from "@halo-dev/components"; import EditorProviderSelector from "@/components/dropdown-selector/EditorProviderSelector.vue"; -import { ref, toRef, watch } from "vue"; +import type { ComputedRef } from "vue"; +import { computed, nextTick, onMounted, provide, ref, toRef, watch } from "vue"; import { useLocalStorage } from "@vueuse/core"; -import type { Post, Content, Snapshot } from "@halo-dev/api-client"; +import type { Content, Post, Snapshot } from "@halo-dev/api-client"; import { randomUUID } from "@/utils/id"; import { contentAnnotations } from "@/constants/annotations"; import { useRouteQuery } from "@vueuse/router"; -import { onMounted } from "vue"; import { apiClient } from "@/utils/api-client"; -import { nextTick } from "vue"; import { useRouter } from "vue-router"; import { useI18n } from "vue-i18n"; import { useMutation } from "@tanstack/vue-query"; -import { computed } from "vue"; import { useSaveKeybinding } from "@console/composables/use-save-keybinding"; import PostCreationModal from "./components/PostCreationModal.vue"; import PostSettingEditModal from "./components/PostSettingEditModal.vue"; import HasPermission from "@/components/permission/HasPermission.vue"; -import { provide } from "vue"; -import type { ComputedRef } from "vue"; import { useSessionKeepAlive } from "@/composables/use-session-keep-alive"; import { usePermission } from "@/utils/permission"; import type { AxiosRequestConfig } from "axios"; import { useContentCache } from "@/composables/use-content-cache"; import { useAutoSaveContent } from "@/composables/use-auto-save-content"; +import { usePostUpdateMutate } from "@uc/modules/contents/posts/composables/use-post-update-mutate"; const router = useRouter(); const { t } = useI18n(); @@ -306,19 +303,20 @@ const isUpdateMode = computed( () => !!formState.value.metadata.creationTimestamp ); +const { mutateAsync: postUpdateMutate } = usePostUpdateMutate(); + const { mutateAsync: handleSave, isLoading: isSaving } = useMutation({ - mutationKey: ["save-post"], + mutationKey: ["uc:save-post-content"], variables: { mute: false, }, mutationFn: async () => { // Update title - // TODO: needs retry if (isTitleChanged.value) { - const { data: updatedPost } = await apiClient.uc.post.updateMyPost({ - name: formState.value.metadata.name, - post: formState.value, + const { data: updatedPost } = await postUpdateMutate({ + postToUpdate: formState.value, }); + formState.value = updatedPost; isTitleChanged.value = false; } @@ -379,7 +377,7 @@ function onPublishPostSuccess() { } const { mutateAsync: handlePublish, isLoading: isPublishing } = useMutation({ - mutationKey: ["publish-post"], + mutationKey: ["uc:publish-post"], mutationFn: async () => { await handleSave({ mute: true }); @@ -403,7 +401,7 @@ const { mutateAsync: handlePublish, isLoading: isPublishing } = useMutation({ const postSettingEditModal = ref(false); async function handleOpenPostSettingEditModal() { - handleSave({ mute: true }); + await handleSave({ mute: true }); await getLatestPost(); postSettingEditModal.value = true; } diff --git a/ui/uc-src/modules/contents/posts/components/PostCreationModal.vue b/ui/uc-src/modules/contents/posts/components/PostCreationModal.vue index 12a1a5ef9..a28d8840a 100644 --- a/ui/uc-src/modules/contents/posts/components/PostCreationModal.vue +++ b/ui/uc-src/modules/contents/posts/components/PostCreationModal.vue @@ -32,7 +32,7 @@ const emit = defineEmits<{ const modal = ref(); const { mutate, isLoading } = useMutation({ - mutationKey: ["create-post"], + mutationKey: ["uc:create-post"], mutationFn: async ({ data }: { data: PostFormState }) => { const post: Post = { apiVersion: "content.halo.run/v1alpha1", diff --git a/ui/uc-src/modules/contents/posts/components/PostSettingEditModal.vue b/ui/uc-src/modules/contents/posts/components/PostSettingEditModal.vue index a036de497..01df2d625 100644 --- a/ui/uc-src/modules/contents/posts/components/PostSettingEditModal.vue +++ b/ui/uc-src/modules/contents/posts/components/PostSettingEditModal.vue @@ -5,9 +5,8 @@ import type { PostFormState } from "../types"; import type { Post } from "@halo-dev/api-client"; import { ref } from "vue"; import { useI18n } from "vue-i18n"; -import { useMutation } from "@tanstack/vue-query"; -import { apiClient } from "@/utils/api-client"; import { toDatetimeLocal } from "@/utils/date"; +import { usePostUpdateMutate } from "@uc/modules/contents/posts/composables/use-post-update-mutate"; const { t } = useI18n(); @@ -25,46 +24,34 @@ const emit = defineEmits<{ const modal = ref(); -const { mutate, isLoading } = useMutation({ - mutationKey: ["edit-post"], - mutationFn: async ({ data }: { data: PostFormState }) => { - const postToUpdate: Post = { - ...props.post, - spec: { - ...props.post.spec, - allowComment: data.allowComment, - categories: data.categories, - cover: data.cover, - excerpt: { - autoGenerate: data.excerptAutoGenerate, - raw: data.excerptRaw, - }, - pinned: data.pinned, - publishTime: data.publishTime, - slug: data.slug, - tags: data.tags, - title: data.title, - visible: data.visible, - }, - }; - const { data: updatedPost } = await apiClient.uc.post.updateMyPost({ - name: props.post.metadata.name, - post: postToUpdate, - }); - return updatedPost; - }, - onSuccess(data) { - Toast.success(t("core.common.toast.save_success")); - emit("success", data); - modal.value.close(); - }, - onError() { - Toast.error(t("core.common.toast.save_failed_and_retry")); - }, -}); +const { mutateAsync, isLoading } = usePostUpdateMutate(); -function onSubmit(data: PostFormState) { - mutate({ data }); +async function onSubmit(data: PostFormState) { + const postToUpdate: Post = { + ...props.post, + spec: { + ...props.post.spec, + allowComment: data.allowComment, + categories: data.categories, + cover: data.cover, + excerpt: { + autoGenerate: data.excerptAutoGenerate, + raw: data.excerptRaw, + }, + pinned: data.pinned, + publishTime: data.publishTime, + slug: data.slug, + tags: data.tags, + title: data.title, + visible: data.visible, + }, + }; + + const { data: newPost } = await mutateAsync({ postToUpdate }); + + Toast.success(t("core.common.toast.save_success")); + emit("success", newPost); + modal.value.close(); } diff --git a/ui/uc-src/modules/contents/posts/composables/use-post-update-mutate.ts b/ui/uc-src/modules/contents/posts/composables/use-post-update-mutate.ts new file mode 100644 index 000000000..20c77d6e2 --- /dev/null +++ b/ui/uc-src/modules/contents/posts/composables/use-post-update-mutate.ts @@ -0,0 +1,38 @@ +import { useMutation } from "@tanstack/vue-query"; +import type { Post } from "@halo-dev/api-client"; +import { apiClient } from "@/utils/api-client"; +import { Toast } from "@halo-dev/components"; +import { useI18n } from "vue-i18n"; + +export function usePostUpdateMutate() { + const { t } = useI18n(); + + return useMutation({ + mutationKey: ["uc:update-post"], + mutationFn: async ({ postToUpdate }: { postToUpdate: Post }) => { + const { data: latestPost } = await apiClient.uc.post.getMyPost({ + name: postToUpdate.metadata.name, + }); + + return await apiClient.uc.post.updateMyPost( + { + name: postToUpdate.metadata.name, + post: { + ...latestPost, + spec: { + ...latestPost.spec, + ...postToUpdate.spec, + }, + }, + }, + { + mute: true, + } + ); + }, + retry: 3, + onError: () => { + Toast.error(t("core.common.toast.save_failed_and_retry")); + }, + }); +}