feat: add retry mechanism for saving posts on the UC end (#5578)

#### 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 端保存文章的操作添加重试机制,防止出现因为锁导致的保存失败问题。
```
pull/5464/head^2
Ryan Wang 2024-03-25 12:38:08 +08:00 committed by GitHub
parent db0fa1e103
commit 78c00a28f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 79 additions and 56 deletions

View File

@ -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;
}

View File

@ -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",

View File

@ -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();
}
</script>

View File

@ -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"));
},
});
}