Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: user feedback on article #2062

Merged
merged 14 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions desk/src/components/icons/ThumbsDownFilledIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M5.61375 0.873535C4.80096 0.873535 4.06901 1.36543 3.76196 2.11799L1.42904 7.8358C0.892289 9.15133 1.86001 10.5913 3.28083 10.5913H6.19537V13.1263C6.19537 14.2309 7.0908 15.1263 8.19537 15.1263H8.49555C8.86191 15.1263 9.19894 14.926 9.374 14.6042L12.4924 8.87124C12.5722 8.7246 12.614 8.56033 12.614 8.3934V2.87354C12.614 1.76897 11.7185 0.873535 10.614 0.873535H5.61375ZM14.721 1.50752C14.721 1.23138 14.4972 1.00752 14.221 1.00752C13.9449 1.00752 13.721 1.23138 13.721 1.50752V7.99239C13.721 8.26853 13.9449 8.49239 14.221 8.49239C14.4972 8.49239 14.721 8.26853 14.721 7.99239V1.50752Z"
fill="#171717"
/>
</svg>
</template>
17 changes: 17 additions & 0 deletions desk/src/components/icons/ThumbsDownIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="16" height="16" fill="#FAFAFA" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.19585 10.5913C7.19585 10.0391 6.74814 9.59135 6.19585 9.59135H3.28132C2.57091 9.59135 2.08705 8.87134 2.35542 8.21357L4.68834 2.49576C4.84187 2.11948 5.20785 1.87354 5.61424 1.87354H10.6145C11.1667 1.87354 11.6145 2.32125 11.6145 2.87354V8.3934L8.49604 14.1263H8.19585C7.64357 14.1263 7.19585 13.6786 7.19585 13.1263V10.5913ZM3.28132 10.5913H5.19585H6.19585V11.5913V13.1263C6.19585 14.2309 7.09128 15.1263 8.19585 15.1263H8.49604C8.8624 15.1263 9.19943 14.926 9.37449 14.6042L12.4929 8.87124C12.5727 8.7246 12.6145 8.56033 12.6145 8.3934V2.87354C12.6145 1.76897 11.719 0.873535 10.6145 0.873535H5.61424C4.80145 0.873535 4.0695 1.36543 3.76244 2.11799L1.42953 7.8358C0.892777 9.15133 1.8605 10.5913 3.28132 10.5913ZM14.7215 1.50752C14.7215 1.23138 14.4977 1.00752 14.2215 1.00752C13.9454 1.00752 13.7215 1.23138 13.7215 1.50752V7.99239C13.7215 8.26853 13.9454 8.49239 14.2215 8.49239C14.4977 8.49239 14.7215 8.26853 14.7215 7.99239V1.50752Z"
fill="#171717"
/>
</svg>
</template>
16 changes: 16 additions & 0 deletions desk/src/components/icons/ThumbsUpFilledIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.3862 15.126C11.199 15.126 11.931 14.6341 12.238 13.8815L14.571 8.16372C15.1077 6.84818 14.14 5.40817 12.7192 5.40817H9.80463V2.87319C9.80463 1.76862 8.9092 0.873189 7.80463 0.873189H7.50445C7.13809 0.873189 6.80106 1.07353 6.626 1.39536L5.14803 4.11246L3.50758 7.12827C3.42781 7.27491 3.38603 7.43918 3.38603 7.60611V13.126C3.38603 14.2305 4.28146 15.126 5.38603 15.126H10.3862ZM1.27896 14.492C1.27896 14.7681 1.50282 14.992 1.77896 14.992C2.0551 14.992 2.27896 14.7681 2.27896 14.492V8.00712C2.27896 7.73098 2.0551 7.50712 1.77896 7.50712C1.50282 7.50712 1.27896 7.73098 1.27896 8.00712V14.492Z"
fill="#171717"
/>
</svg>
</template>
16 changes: 16 additions & 0 deletions desk/src/components/icons/ThumbsUpIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<svg
width="14"
height="16"
viewBox="0 0 14 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.80415 5.40817C7.80415 5.96045 8.25186 6.40817 8.80415 6.40817H11.7187C12.4291 6.40817 12.913 7.12817 12.6446 7.78594L10.3117 13.5038C10.1581 13.88 9.79215 14.126 9.38576 14.126H4.38554C3.83325 14.126 3.38554 13.6783 3.38554 13.126V7.60611L6.50396 1.87319H6.80415C7.35643 1.87319 7.80415 2.3209 7.80415 2.87319V5.40817ZM11.7187 5.40817H9.80415H8.80415V4.40817V2.87319C8.80415 1.76862 7.90872 0.873189 6.80415 0.873189H6.50396C6.1376 0.873189 5.80057 1.07353 5.62551 1.39536L2.50709 7.12827C2.42732 7.27491 2.38554 7.43918 2.38554 7.60611V13.126C2.38554 14.2305 3.28097 15.126 4.38554 15.126H9.38576C10.1985 15.126 10.9305 14.6341 11.2376 13.8815L13.5705 8.16372C14.1072 6.84818 13.1395 5.40817 11.7187 5.40817ZM0.27847 14.492C0.27847 14.7681 0.502328 14.992 0.77847 14.992C1.05461 14.992 1.27847 14.7681 1.27847 14.492V8.00712C1.27847 7.73098 1.05461 7.50712 0.77847 7.50712C0.502328 7.50712 0.27847 7.73098 0.27847 8.00712V14.492Z"
fill="#171717"
/>
</svg>
</template>
4 changes: 4 additions & 0 deletions desk/src/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ export { default as ReplyIcon } from "./ReplyIcon.vue";
export { default as ReplyAllIcon } from "./ReplyAllIcon.vue";
export { default as RefreshIcon } from "./RefreshIcon.vue";
export { default as DetailsIcon } from "./DetailsIcon.vue";
export { default as ThumbsUpIcon } from "./ThumbsUpIcon.vue";
export { default as ThumbsUpFilledIcon } from "./ThumbsUpFilledIcon.vue";
export { default as ThumbsDownIcon } from "./ThumbsDownIcon.vue";
export { default as ThumbsDownFilledIcon } from "./ThumbsDownFilledIcon.vue";
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<!-- Top Section -->
<section class="flex flex-col gap-3.5 mb-5">
<h3 class="text-2xl font-semibold text-gray-800">
{{ category.categoryName }}
{{ category.categoryName || "Getting Started" }}
</h3>
<FormControl
type="text"
Expand Down Expand Up @@ -59,6 +59,7 @@
:article="article"
:author="category.authors[article.author]"
:key="article.name"
class="hover:bg-gray-200 transition-all hover:rounded"
/>
</div>
</section>
Expand Down
114 changes: 85 additions & 29 deletions desk/src/pages/KnowledgeBaseArticle.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="flex h-full flex-col overflow-hidden">
<div class="flex h-full flex-col overflow-scroll">
<LayoutHeader>
<template #left-header>
<Breadcrumbs :items="breadcrumbs" />
Expand All @@ -18,8 +18,8 @@
/>
</template>
</LayoutHeader>
<div class="overflow-auto mx-auto w-full max-w-4xl px-5">
<div class="py-6">
<div class="mx-auto w-full max-w-4xl px-24">
<div class="py-6 flex flex-col gap-5">
<TextEditor
:content="textEditorContentWithIDs"
:editable="editMode"
Expand All @@ -30,44 +30,63 @@
shadow: editMode,
'p-4': editMode,
}"
editor-class="prose-f"
editor-class="prose-f first:mt-3"
@change="articleContent = $event"
>
<template #top v-if="!route.meta.public">
<template #top>
<component
:is="topComponent"
v-model:title="articleTitle"
v-bind="options__"
/>

<TextEditorFixedMenu
v-if="editMode"
class="-ml-1"
:buttons="textEditorMenuButtons"
/>
</template>
</TextEditor>
<!-- <div
v-else-if="!article.data && !editMode"
class="text-gray-500 text-2xl font-semibold mb-5"
>
Article Not Found
</div> -->
<RouterLink
v-if="route.meta.public"
:to="{ name: CUSTOMER_PORTAL_NEW_TICKET }"
class=""

<div
class="flex items-center justify-between mb-8 p-4 rounded-lg bg-gray-50"
v-if="isCustomerPortal"
>
<Button
label="Still need help? Create a ticket"
size="md"
theme="gray"
variant="solid"
class="mt-5"
>
<template #suffix> &rightarrow; </template>
</Button>
</RouterLink>
<!-- Feedback Section -->
<div>
<!-- was this article helpful? -->
<div class="flex items-center gap-2">
<span class="text-gray-800 text-sm"
>Did this article solve your issue?</span
>
<div class="flex items-center gap-1">
<component
:is="userFeedback === 1 ? ThumbsUpFilledIcon : ThumbsUpIcon"
class="w-4 h-4 cursor-pointer"
@click="handleFeedbackClick('Like')"
/>
<component
:is="
userFeedback === 2 ? ThumbsDownFilledIcon : ThumbsDownIcon
"
class="w-4 h-4 cursor-pointer"
@click="handleFeedbackClick('Dislike')"
/>
</div>
</div>
</div>
<!-- Create a ticket CTA -->
<div class="flex items-center justify-center gap-2">
<span class="font-normal text-sm">
Can’t find what you’re looking for?
</span>
<RouterLink :to="{ name: CUSTOMER_PORTAL_NEW_TICKET }">
<p class="underline font-bold text-sm">
Create a ticket &rightarrow;
<!-- <template #suffix> </template> -->
</p>
</RouterLink>
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -101,8 +120,15 @@ import KnowledgeBaseArticleActionsView from "./knowledge-base/KnowledgeBaseArtic
import KnowledgeBaseArticleTopEdit from "./knowledge-base/KnowledgeBaseArticleTopEdit.vue";
import KnowledgeBaseArticleTopNew from "./knowledge-base/KnowledgeBaseArticleTopNew.vue";
import KnowledgeBaseArticleTopView from "./knowledge-base/KnowledgeBaseArticleTopView.vue";
import { PreserveIds } from "@/tiptap-extensions";

import { PreserveIds } from "@/tiptap-extensions";
import { FeedbackAction } from "@/types";
import {
ThumbsUpIcon,
ThumbsUpFilledIcon,
ThumbsDownIcon,
ThumbsDownFilledIcon,
} from "@/components/icons";
const props = defineProps({
articleId: {
type: String,
Expand Down Expand Up @@ -146,7 +172,7 @@ const breadcrumbs = computed(() => {
},
},
];
// if (options__.value.subCategoryId !== options__.value.categoryId) {

agentPortalItems.push({
label: options__.value.subCategoryName,
route: {
Expand All @@ -158,7 +184,6 @@ const breadcrumbs = computed(() => {
},
},
});
// }

if (!isNew) {
agentPortalItems.push({
Expand Down Expand Up @@ -224,13 +249,15 @@ const topComponent = computed(() => {
return KnowledgeBaseArticleTopView;
});

const userFeedback = ref<FeedbackAction>(null);
const article = createResource({
url: "helpdesk.helpdesk.doctype.hd_article.api.get_article",
params: {
name: props.articleId,
},
onSuccess(data) {
articleTitle.value = data.title;
userFeedback.value = data.user_feedback;
capture("article_viewed", {
data: {
user: authStore.userId,
Expand All @@ -242,6 +269,35 @@ const article = createResource({
auto: !isNew,
});

const setFeedback = createResource({
url: "run_doc_method",
debounce: 300,
makeParams: () => ({
dt: "HD Article",
dn: props.articleId,
method: "set_feedback",
args: {
value: userAction.value,
},
}),
onSuccess: () => {
article.reload();
},
});

const userAction = ref();
const feedbackMap = {
Remove: 0,
Like: 1,
Dislike: 2,
};

function handleFeedbackClick(action: string) {
userAction.value = feedbackMap[action];
userFeedback.value = feedbackMap[action];
setFeedback.submit();
}

const category = createDocumentResource({
doctype: "HD Article Category",
name: categoryId.value,
Expand Down
24 changes: 15 additions & 9 deletions desk/src/pages/knowledge-base/KnowledgeBaseArticleTopView.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div class="mb-4.5 flex items-center justify-between">
<div class="mb-7 flex items-center justify-between">
<div class="flex items-center gap-1">
<div class="flex items-center gap-2">
<Avatar :label="authorFullname" :image="authorImage" />
Expand All @@ -8,16 +8,18 @@
</div>
</div>
<IconDot class="h-4 w-4 text-gray-600" />
<div class="text-xs text-gray-800">
<div class="text-xs text-gray-500">
{{ dayjs(creation).short() }}
</div>
<IconDot class="h-4 w-4 text-gray-600" />
<Badge
:theme="status === 'Published' ? 'green' : 'orange'"
variant="subtle"
>
{{ status }}
</Badge>
<div v-if="!isCustomerPortal" class="flex items-center justify-center">
<IconDot class="h-4 w-4 text-gray-600" />
<Badge
:theme="status === 'Published' ? 'green' : 'orange'"
variant="subtle"
>
{{ status }}
</Badge>
</div>
</div>
</div>
<div class="border-b pb-3">
Expand All @@ -31,6 +33,10 @@
import { Avatar } from "frappe-ui";
import { dayjs } from "@/dayjs";
import IconDot from "~icons/lucide/dot";
import { useRoute } from "vue-router";

const route = useRoute();
const isCustomerPortal: boolean = route.meta.public ?? false;

const props = defineProps({
categoryName: {
Expand Down
2 changes: 2 additions & 0 deletions desk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,3 +246,5 @@ export interface Category {
};
children?: (Article | SubCategory)[];
}

export type FeedbackAction = 0 | 1 | 2; // 0: neutral, 1: like, 2: dislike
2 changes: 1 addition & 1 deletion helpdesk/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.0"
__version__ = "1.1.0"
13 changes: 13 additions & 0 deletions helpdesk/helpdesk/doctype/hd_article/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@ def get_article(name: str):
"HD Article Category", sub_category.parent_category or article["category"]
)

user = frappe.session.user
# TODO: views count increment with views field in HD Article
# if not is_agent() and user != author.name:
# frappe.db.set_value("HD Article", name, "views", article["views"] + 1)

user_feedback = int(
frappe.db.get_value(
"HD Article Feedback", {"user": user, "article": name}, "feedback"
)
or 0
)

return {
**article,
"author": author,
"category": category,
"sub_category": sub_category,
"user_feedback": user_feedback,
}
2 changes: 1 addition & 1 deletion helpdesk/helpdesk/doctype/hd_article/hd_article.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
],
"links": [],
"make_attachments_public": 1,
"modified": "2024-10-01 21:39:58.189965",
"modified": "2024-11-25 13:02:28.644492",
"modified_by": "Administrator",
"module": "Helpdesk",
"name": "HD Article",
Expand Down
15 changes: 15 additions & 0 deletions helpdesk/helpdesk/doctype/hd_article/hd_article.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,21 @@ def before_save(self):
)
)

@frappe.whitelist()
def set_feedback(self, value):
# 0 empty, 1 like, 2 dislike
user = frappe.session.user
feedback = frappe.db.exists(
"HD Article Feedback", {"user": user, "article": self.name}
)
if feedback:
frappe.db.set_value("HD Article Feedback", feedback, "feedback", value)
return

frappe.new_doc(
"HD Article Feedback", user=user, article=self.name, feedback=value
).insert()

@property
def title_slug(self) -> str:
"""
Expand Down
Loading
Loading