From 73c59050a41139603b167c2fcc174f7132f47330 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Wed, 25 Oct 2023 13:44:12 +0530 Subject: [PATCH 01/20] refactor: comment --- .../doctype/hd_attachment/__init__.py | 0 .../doctype/hd_attachment/hd_attachment.json | 39 +++++++++ .../doctype/hd_attachment/hd_attachment.py | 9 +++ .../helpdesk/doctype/hd_comment/__init__.py | 0 .../helpdesk/doctype/hd_comment/hd_comment.js | 8 ++ .../doctype/hd_comment/hd_comment.json | 81 +++++++++++++++++++ .../helpdesk/doctype/hd_comment/hd_comment.py | 9 +++ .../doctype/hd_comment/test_hd_comment.py | 9 +++ .../helpdesk/doctype/hd_ticket/hd_ticket.py | 13 --- helpdesk/hooks.py | 3 +- helpdesk/perms.py | 22 +++++ 11 files changed, 179 insertions(+), 14 deletions(-) create mode 100644 helpdesk/helpdesk/doctype/hd_attachment/__init__.py create mode 100644 helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.json create mode 100644 helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.py create mode 100644 helpdesk/helpdesk/doctype/hd_comment/__init__.py create mode 100644 helpdesk/helpdesk/doctype/hd_comment/hd_comment.js create mode 100644 helpdesk/helpdesk/doctype/hd_comment/hd_comment.json create mode 100644 helpdesk/helpdesk/doctype/hd_comment/hd_comment.py create mode 100644 helpdesk/helpdesk/doctype/hd_comment/test_hd_comment.py create mode 100644 helpdesk/perms.py diff --git a/helpdesk/helpdesk/doctype/hd_attachment/__init__.py b/helpdesk/helpdesk/doctype/hd_attachment/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.json b/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.json new file mode 100644 index 000000000..12c3f215d --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-10-25 13:05:28.308704", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "column_break_btyv", + "file" + ], + "fields": [ + { + "fieldname": "column_break_btyv", + "fieldtype": "Column Break", + "options": "File" + }, + { + "fieldname": "file", + "fieldtype": "Link", + "in_list_view": 1, + "label": "File", + "options": "File", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-10-25 13:05:28.308704", + "modified_by": "Administrator", + "module": "Helpdesk", + "name": "HD Attachment", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.py b/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.py new file mode 100644 index 000000000..c221a5cfa --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_attachment/hd_attachment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class HDAttachment(Document): + pass diff --git a/helpdesk/helpdesk/doctype/hd_comment/__init__.py b/helpdesk/helpdesk/doctype/hd_comment/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/helpdesk/helpdesk/doctype/hd_comment/hd_comment.js b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.js new file mode 100644 index 000000000..be9c780d7 --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2023, Frappe Technologies and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("HD Comment", { +// refresh(frm) { + +// }, +// }); diff --git a/helpdesk/helpdesk/doctype/hd_comment/hd_comment.json b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.json new file mode 100644 index 000000000..574039770 --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.json @@ -0,0 +1,81 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2023-10-25 13:05:31.915738", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "reference_section", + "reference_doctype", + "column_break_ehal", + "reference_name", + "section_break_vdrv", + "type", + "content", + "attachments" + ], + "fields": [ + { + "fieldname": "reference_section", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "reference_doctype", + "fieldtype": "Link", + "in_list_view": 1, + "label": "DocType", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "column_break_ehal", + "fieldtype": "Column Break" + }, + { + "fieldname": "reference_name", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Name", + "options": "reference_doctype", + "reqd": 1 + }, + { + "fieldname": "section_break_vdrv", + "fieldtype": "Section Break" + }, + { + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Public\nPrivate", + "reqd": 1 + }, + { + "fieldname": "content", + "fieldtype": "Long Text", + "in_list_view": 1, + "label": "Content", + "reqd": 1 + }, + { + "fieldname": "attachments", + "fieldtype": "Table MultiSelect", + "label": "Attachments", + "options": "HD Attachment" + } + ], + "links": [], + "modified": "2023-10-25 13:05:31.915738", + "modified_by": "Administrator", + "module": "Helpdesk", + "name": "HD Comment", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/helpdesk/helpdesk/doctype/hd_comment/hd_comment.py b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.py new file mode 100644 index 000000000..2a5dfbdad --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_comment/hd_comment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class HDComment(Document): + pass diff --git a/helpdesk/helpdesk/doctype/hd_comment/test_hd_comment.py b/helpdesk/helpdesk/doctype/hd_comment/test_hd_comment.py new file mode 100644 index 000000000..81f5f5e29 --- /dev/null +++ b/helpdesk/helpdesk/doctype/hd_comment/test_hd_comment.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestHDComment(FrappeTestCase): + pass diff --git a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py index 6f646a256..1e13df83a 100644 --- a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py +++ b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py @@ -677,16 +677,3 @@ def on_communication_update(self, c): self.description = self.description or c.content # Save the ticket, allowing for hooks to run. self.save() - - -# Check if `user` has access to this specific ticket (`doc`). This implements extra -# permission checks which is not possible with standard permission system. This function -# is being called from hooks. `doc` is the ticket to check against -def has_permission(doc, user=None): - return ( - doc.contact == user - or doc.raised_by == user - or doc.owner == user - or is_agent(user) - or doc.customer in get_customer(user) - ) diff --git a/helpdesk/hooks.py b/helpdesk/hooks.py index 63d9545a4..727a10ecd 100644 --- a/helpdesk/hooks.py +++ b/helpdesk/hooks.py @@ -33,5 +33,6 @@ } has_permission = { - "HD Ticket": "helpdesk.helpdesk.doctype.hd_ticket.hd_ticket.has_permission", + "HD Ticket": "helpdesk.perms.ticket", + "HD Comment": "helpdesk.perms.comment", } diff --git a/helpdesk/perms.py b/helpdesk/perms.py new file mode 100644 index 000000000..fd5a96916 --- /dev/null +++ b/helpdesk/perms.py @@ -0,0 +1,22 @@ +import frappe + +from helpdesk.utils import get_customer, is_agent + + +# Check if `user` has access to this specific ticket (`doc`). This implements extra +# permission checks which is not possible with standard permission system. This function +# is being called from hooks. `doc` is the ticket to check against +def ticket(doc, user=None): + return ( + doc.contact == user + or doc.raised_by == user + or doc.owner == user + or is_agent(user) + or doc.customer in get_customer(user) + ) + + +# Check if `user` has access to this specific comment (`doc`). This can be done by +# checking if parent doc (ticket) is accessible by `user`. +def comment(doc): + return frappe.get_doc(doc.reference_doctype, doc.reference_name).has_permission() From f10d1e540a90f080d1d10d09ffc3969e6d2b6b31 Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Mon, 30 Oct 2023 19:48:42 +0530 Subject: [PATCH 02/20] chore: step 1 --- ...etComment.vue => TicketCommentPrivate.vue} | 0 ...munication.vue => TicketCommentPublic.vue} | 0 desk/src/pages/ticket/TicketConversation.vue | 85 +++++++++-------- desk/src/pages/ticket/TicketCustomer.vue | 13 +-- desk/src/pages/ticket/TicketTextEditor.vue | 2 +- .../doctype/hd_comment/hd_comment.json | 91 +++++++++++++++--- .../helpdesk/doctype/hd_comment/hd_comment.py | 92 ++++++++++++++++++- 7 files changed, 222 insertions(+), 61 deletions(-) rename desk/src/pages/ticket/{TicketComment.vue => TicketCommentPrivate.vue} (100%) rename desk/src/pages/ticket/{TicketCommunication.vue => TicketCommentPublic.vue} (100%) diff --git a/desk/src/pages/ticket/TicketComment.vue b/desk/src/pages/ticket/TicketCommentPrivate.vue similarity index 100% rename from desk/src/pages/ticket/TicketComment.vue rename to desk/src/pages/ticket/TicketCommentPrivate.vue diff --git a/desk/src/pages/ticket/TicketCommunication.vue b/desk/src/pages/ticket/TicketCommentPublic.vue similarity index 100% rename from desk/src/pages/ticket/TicketCommunication.vue rename to desk/src/pages/ticket/TicketCommentPublic.vue diff --git a/desk/src/pages/ticket/TicketConversation.vue b/desk/src/pages/ticket/TicketConversation.vue index 7992f1c76..830421cae 100644 --- a/desk/src/pages/ticket/TicketConversation.vue +++ b/desk/src/pages/ticket/TicketConversation.vue @@ -1,20 +1,19 @@ - + diff --git a/desk/src/pages/ticket/TicketCustomer.vue b/desk/src/pages/ticket/TicketCustomer.vue index 9d48d7ffb..7785a0140 100644 --- a/desk/src/pages/ticket/TicketCustomer.vue +++ b/desk/src/pages/ticket/TicketCustomer.vue @@ -87,17 +87,18 @@ const showFeedbackDialog = ref(false); const isExpanded = ref(false); const send = createResource({ - url: "run_doc_method", + url: "frappe.client.insert", debounce: 300, makeParams: () => ({ - dt: "HD Ticket", - dn: props.ticketId, - method: "create_communication_via_contact", - args: { - message: editorContent.value, + doc: { + doctype: "HD Comment", + content: editorContent.value, attachments: attachments.value, + reference_doctype: "HD Ticket", + reference_name: props.ticketId, }, }), + auto: false, onSuccess: () => { editor.value.editor.commands.clearContent(true); attachments.value = []; diff --git a/desk/src/pages/ticket/TicketTextEditor.vue b/desk/src/pages/ticket/TicketTextEditor.vue index 826519410..0ab6887e0 100644 --- a/desk/src/pages/ticket/TicketTextEditor.vue +++ b/desk/src/pages/ticket/TicketTextEditor.vue @@ -33,7 +33,7 @@ Date: Tue, 31 Oct 2023 00:29:41 +0530 Subject: [PATCH 03/20] chore: step 2 --- desk/src/composables/index.ts | 2 + desk/src/pages/index.ts | 2 +- desk/src/pages/ticket/TicketActionsTags.vue | 24 +- desk/src/pages/ticket/TicketAgent.vue | 57 +-- desk/src/pages/ticket/TicketBreadcrumbs.vue | 15 +- .../src/pages/ticket/TicketCommentPrivate.vue | 2 +- desk/src/pages/ticket/TicketConversation.vue | 25 +- desk/src/pages/ticket/TicketDetails.vue | 77 ++-- desk/src/pages/ticket/TicketHistory.vue | 21 +- .../src/pages/ticket/TicketPinnedComments.vue | 11 +- desk/src/pages/ticket/TicketView.vue | 332 ++++++++++++++++++ desk/src/pages/ticket/TicketViews.vue | 13 +- desk/src/pages/ticket/symbols.ts | 7 +- .../hd_ticket_template/hd_ticket_template.py | 14 + 14 files changed, 467 insertions(+), 135 deletions(-) create mode 100644 desk/src/pages/ticket/TicketView.vue diff --git a/desk/src/composables/index.ts b/desk/src/composables/index.ts index 74b82f35c..8b871f4b0 100644 --- a/desk/src/composables/index.ts +++ b/desk/src/composables/index.ts @@ -1 +1,3 @@ +export { createListManager } from "./listManager"; export { useDevice } from "./device"; +export { useError } from "./error"; diff --git a/desk/src/pages/index.ts b/desk/src/pages/index.ts index 9134c4c47..81cb22df5 100644 --- a/desk/src/pages/index.ts +++ b/desk/src/pages/index.ts @@ -10,7 +10,7 @@ export { default as KBHome } from "./knowledge-base/KnowledgeBasePublicHome.vue" export { default as KBCategoryPublic } from "./knowledge-base/KnowledgeBasePublicCategory.vue"; export { default as KBArticlePublic } from "./knowledge-base/KnowledgeBaseArticle.vue"; -export { default as TicketAgent } from "./ticket/TicketAgent.vue"; +export { default as TicketAgent } from "./ticket/TicketView.vue"; export { default as TicketCustomer } from "./ticket/TicketCustomer.vue"; export { default as TicketNew } from "./ticket/TicketNew.vue"; export { default as TicketsAgent } from "./tickets/TicketsAgent.vue"; diff --git a/desk/src/pages/ticket/TicketActionsTags.vue b/desk/src/pages/ticket/TicketActionsTags.vue index cd8966782..76bddeb32 100644 --- a/desk/src/pages/ticket/TicketActionsTags.vue +++ b/desk/src/pages/ticket/TicketActionsTags.vue @@ -27,14 +27,14 @@
Tags
{ - ticket.reload().then(() => (showInput.value = false)); + tags.reload().then(() => (showInput.value = false)); }, }); const rmTag = createResource({ @@ -117,7 +127,7 @@ const rmTag = createResource({ dt: "HD Ticket", dn: ticket.data.name, }), - onSuccess: () => ticket.reload(), + onSuccess: () => tags.reload(), }); /** diff --git a/desk/src/pages/ticket/TicketAgent.vue b/desk/src/pages/ticket/TicketAgent.vue index 89cca059d..20a6f6a7c 100644 --- a/desk/src/pages/ticket/TicketAgent.vue +++ b/desk/src/pages/ticket/TicketAgent.vue @@ -155,8 +155,8 @@ " theme="gray" variant="solid" - :disabled="$refs.editor.editor.isEmpty || resource.loading" - @click="() => resource.submit()" + :disabled="$refs.editor.editor.isEmpty || send.loading" + @click="() => send.submit()" /> @@ -177,7 +177,7 @@ diff --git a/desk/src/pages/ticket/TicketCommentPrivate.vue b/desk/src/pages/ticket/TicketCommentPrivate.vue index 2349d4fd9..4d8f6b18f 100644 --- a/desk/src/pages/ticket/TicketCommentPrivate.vue +++ b/desk/src/pages/ticket/TicketCommentPrivate.vue @@ -74,7 +74,7 @@ const options = computed(() => const togglePin = createResource({ url: "frappe.client.set_value", makeParams: () => ({ - doctype: "HD Ticket Comment", + doctype: "HD Comment", name: name.value, fieldname: "is_pinned", value: !isPinned.value, diff --git a/desk/src/pages/ticket/TicketConversation.vue b/desk/src/pages/ticket/TicketConversation.vue index 830421cae..9b24349ff 100644 --- a/desk/src/pages/ticket/TicketConversation.vue +++ b/desk/src/pages/ticket/TicketConversation.vue @@ -30,7 +30,7 @@ import { inject } from "vue"; import TicketCommentPrivate from "./TicketCommentPrivate.vue"; import TicketCommentPublic from "./TicketCommentPublic.vue"; -import { ITicket } from "./symbols"; +import { ITicket, Comments } from "./symbols"; import { createListManager } from "@/composables/listManager"; interface P { @@ -41,28 +41,7 @@ const props = withDefaults(defineProps

(), { focus: "", }); const ticket = inject(ITicket); - -const comments = createListManager({ - doctype: "HD Comment", - cache: "TicketComments", - fields: [ - "name", - "comment_type", - "content", - "creation", - "is_pinned", - "user", - { - attachments: ["file.file_url", "file.file_name"], - }, - ], - filters: { - reference_doctype: "HD Ticket", - reference_name: ticket.data.name, - }, - orderBy: "creation asc", - auto: true, -}); +const comments = inject(Comments); // function scroll(id: string) { // const e = document.getElementById(id); diff --git a/desk/src/pages/ticket/TicketDetails.vue b/desk/src/pages/ticket/TicketDetails.vue index 7646db774..e1fd66512 100644 --- a/desk/src/pages/ticket/TicketDetails.vue +++ b/desk/src/pages/ticket/TicketDetails.vue @@ -7,7 +7,7 @@

ID - {{ data.name }} + {{ id }}
@@ -79,16 +79,16 @@
Modified - + - {{ dayjs(ticket.data.modified).fromNow() }} + {{ dayjs(data.modified).fromNow() }}
Source - {{ ticket.data.via_customer_portal ? "Portal" : "Mail" }} + {{ data.via_customer_portal ? "Portal" : "Mail" }}
@@ -119,15 +119,17 @@ :options="o.store.dropdown" :placeholder="`Select a ${o.label}`" :value="data[o.field]" - @change="update(o.field, $event.value)" + @change="(e) => update.submit({ fieldname: o.field, value: e.value })" />
@@ -135,7 +137,12 @@ diff --git a/desk/src/pages/ticket/TicketView.vue b/desk/src/pages/ticket/TicketView.vue index d6482fc70..60c16a150 100644 --- a/desk/src/pages/ticket/TicketView.vue +++ b/desk/src/pages/ticket/TicketView.vue @@ -175,7 +175,7 @@ diff --git a/desk/src/pages/tickets/TicketsAgentList.vue b/desk/src/pages/tickets/TicketsAgentList.vue index 6ae262406..9fc3a53c3 100644 --- a/desk/src/pages/tickets/TicketsAgentList.vue +++ b/desk/src/pages/tickets/TicketsAgentList.vue @@ -10,7 +10,7 @@ diff --git a/desk/src/pages/tickets/TicketsAgent.vue b/desk/src/pages/tickets/TicketsAgent.vue index 7d7eb9530..809085024 100644 --- a/desk/src/pages/tickets/TicketsAgent.vue +++ b/desk/src/pages/tickets/TicketsAgent.vue @@ -50,10 +50,6 @@ const pageLength = ref(20); const tickets = createListManager({ doctype: 'HD Ticket', pageLength: pageLength.value, - filters: { - status: 'Resolved', - agent_group: 'Random Team', - }, orderBy: getOrder(), auto: true, transform: (data) => { diff --git a/desk/src/pages/tickets/TicketsAgentList.vue b/desk/src/pages/tickets/TicketsAgentList.vue index 9fc3a53c3..eeb3f180e 100644 --- a/desk/src/pages/tickets/TicketsAgentList.vue +++ b/desk/src/pages/tickets/TicketsAgentList.vue @@ -10,7 +10,7 @@ - - diff --git a/desk/src/resources/meta.ts b/desk/src/resources/meta.ts index 95788bf82..f67ca63d9 100644 --- a/desk/src/resources/meta.ts +++ b/desk/src/resources/meta.ts @@ -50,8 +50,10 @@ export const fetch = createResource({ onError: useError(), onSuccess: (data) => { data.fields = data.fields - // Remove hidden fields - .filter((f) => !f.hidden) + // Remove hide fields + .filter((f) => !f.hide) + // Sort fields by `label` + .sort((a, b) => a.label?.localeCompare(b.label)) // Sort fields by `index` .sort((a, b) => a.index - b.index); state[data.name] = data; @@ -68,29 +70,57 @@ const override = computed(() => ({ subject: { width: 'w-96', text: 'text-gray-900', + index: 2, }, status: { width: 'w-20', + index: 3, + }, + priority: { + width: 'w-20', + index: 4, + }, + ticket_type: { + label: 'Type', + index: 5, }, assignee: { label: 'Assignee', width: 'w-40', - hidden: !authStore.isAgent, + index: 6, + hide: !authStore.isAgent, }, conversation: { label: 'Conversation', width: 'w-32', - hidden: !authStore.isAgent, + index: 7, + hide: !authStore.isAgent, }, source: { label: 'Source', - hidden: !authStore.isAgent, + hide: !authStore.isAgent, }, creation: { label: 'Created', + type: 'timeAgo', + index: 100, }, modified: { label: 'Updated', + type: 'timeAgo', + index: 101, + }, + agent_group: { + hide: !authStore.isAgent, + }, + response_by: { + hide: !authStore.isAgent, + }, + resolution_by: { + hide: !authStore.isAgent, + }, + agreement_status: { + hide: !authStore.isAgent, }, }, })); diff --git a/desk/src/router/index.js b/desk/src/router/index.js index 2d2cdf560..b2c27450f 100644 --- a/desk/src/router/index.js +++ b/desk/src/router/index.js @@ -1,65 +1,65 @@ -import { createRouter, createWebHistory } from "vue-router"; -import { useAuthStore } from "@/stores/auth"; -import { init as initTelemetry } from "@/telemetry"; -import { AuthPages } from "./auth"; -import { CustomerPages } from "./customer"; -import { KnowldegeBasePages } from "./knowledege-base"; -import { getPage } from "./utils"; +import { createRouter, createWebHistory } from 'vue-router'; +import { useAuthStore } from '@/stores/auth'; +import { init as initTelemetry } from '@/telemetry'; +import { AuthPages } from './auth'; +import { CustomerPages } from './customer'; +import { KnowldegeBasePages } from './knowledege-base'; +import { getPage } from './utils'; -export const WEBSITE_ROOT = "Website Root"; +export const WEBSITE_ROOT = 'Website Root'; -export const LOGIN = "AuthLogin"; -export const SIGNUP = "AuthSignup"; -export const VERIFY = "AuthVerify"; +export const LOGIN = 'AuthLogin'; +export const SIGNUP = 'AuthSignup'; +export const VERIFY = 'AuthVerify'; export const AUTH_ROUTES = [LOGIN, SIGNUP, VERIFY]; -export const ONBOARDING_PAGE = "Setup"; +export const ONBOARDING_PAGE = 'Setup'; -export const CUSTOMER_PORTAL_NEW_TICKET = "TicketNew"; -export const CUSTOMER_PORTAL_TICKET = "TicketCustomer"; +export const CUSTOMER_PORTAL_NEW_TICKET = 'TicketNew'; +export const CUSTOMER_PORTAL_TICKET = 'TicketCustomer'; -export const AGENT_PORTAL_AGENT_LIST = "AgentList"; -export const AGENT_PORTAL_CANNED_RESPONSE_LIST = "CannedResponses"; -export const AGENT_PORTAL_CANNED_RESPONSE_SINGLE = "CannedResponse"; -export const AGENT_PORTAL_CONTACT_LIST = "ContactList"; -export const AGENT_PORTAL_CUSTOMER_LIST = "CustomerList"; -export const AGENT_PORTAL_DASHBOARD = "DeskDashboard"; -export const AGENT_PORTAL_ESCALATION_RULE_LIST = "EscalationRules"; -export const AGENT_PORTAL_TEAM_LIST = "Teams"; -export const AGENT_PORTAL_TEAM_SINGLE = "Team"; -export const AGENT_PORTAL_TICKET = "TicketAgent"; -export const AGENT_PORTAL_TICKET_LIST = "TicketsAgent"; -export const AGENT_PORTAL_TICKET_TYPE_LIST = "TicketTypes"; -export const AGENT_PORTAL_TICKET_TYPE_NEW = "NewTicketType"; -export const AGENT_PORTAL_TICKET_TYPE_SINGLE = "TicketType"; -export const AGENT_PORTAL_KNOWLEDGE_BASE = "DeskKBHome"; -export const AGENT_PORTAL_KNOWLEDGE_BASE_CATEGORY = "DeskKBCategory"; -export const AGENT_PORTAL_KNOWLEDGE_BASE_SUB_CATEGORY = "DeskKBSubcategory"; -export const AGENT_PORTAL_KNOWLEDGE_BASE_ARTICLE = "DeskKBArticle"; +export const AGENT_PORTAL_AGENT_LIST = 'AgentList'; +export const AGENT_PORTAL_CANNED_RESPONSE_LIST = 'CannedResponses'; +export const AGENT_PORTAL_CANNED_RESPONSE_SINGLE = 'CannedResponse'; +export const AGENT_PORTAL_CONTACT_LIST = 'ContactList'; +export const AGENT_PORTAL_CUSTOMER_LIST = 'CustomerList'; +export const AGENT_PORTAL_DASHBOARD = 'DeskDashboard'; +export const AGENT_PORTAL_ESCALATION_RULE_LIST = 'EscalationRules'; +export const AGENT_PORTAL_TEAM_LIST = 'Teams'; +export const AGENT_PORTAL_TEAM_SINGLE = 'Team'; +export const AGENT_PORTAL_TICKET = 'TicketAgent'; +export const AGENT_PORTAL_TICKET_LIST = 'TicketsAgent'; +export const AGENT_PORTAL_TICKET_TYPE_LIST = 'TicketTypes'; +export const AGENT_PORTAL_TICKET_TYPE_NEW = 'NewTicketType'; +export const AGENT_PORTAL_TICKET_TYPE_SINGLE = 'TicketType'; +export const AGENT_PORTAL_KNOWLEDGE_BASE = 'DeskKBHome'; +export const AGENT_PORTAL_KNOWLEDGE_BASE_CATEGORY = 'DeskKBCategory'; +export const AGENT_PORTAL_KNOWLEDGE_BASE_SUB_CATEGORY = 'DeskKBSubcategory'; +export const AGENT_PORTAL_KNOWLEDGE_BASE_ARTICLE = 'DeskKBArticle'; -export const KB_PUBLIC = "KBHome"; -export const KB_PUBLIC_ARTICLE = "KBArticlePublic"; -export const KB_PUBLIC_CATEGORY = "KBCategoryPublic"; +export const KB_PUBLIC = 'KBHome'; +export const KB_PUBLIC_ARTICLE = 'KBArticlePublic'; +export const KB_PUBLIC_CATEGORY = 'KBCategoryPublic'; -export const CUSTOMER_PORTAL_LANDING = "TicketsCustomer"; +export const CUSTOMER_PORTAL_LANDING = 'TicketsCustomer'; export const AGENT_PORTAL_LANDING = AGENT_PORTAL_TICKET_LIST; const routes = [ { - path: "", - component: () => getPage("HRoot"), + path: '', + component: () => getPage('HRoot'), }, AuthPages, CustomerPages, KnowldegeBasePages, { - path: "/onboarding", + path: '/onboarding', name: ONBOARDING_PAGE, - component: () => import("@/pages/onboarding/SimpleOnboarding.vue"), + component: () => import('@/pages/onboarding/SimpleOnboarding.vue'), }, { - path: "", - name: "AgentRoot", - component: () => import("@/pages/desk/AgentRoot.vue"), + path: '', + name: 'AgentRoot', + component: () => import('@/pages/desk/AgentRoot.vue'), meta: { auth: true, agent: true, @@ -67,126 +67,126 @@ const routes = [ }, children: [ { - path: "dashboard", + path: 'dashboard', name: AGENT_PORTAL_DASHBOARD, - component: () => import("@/pages/desk/DeskDashboard.vue"), + component: () => import('@/pages/desk/DeskDashboard.vue'), }, { - path: "tickets", + path: 'tickets', name: AGENT_PORTAL_TICKET_LIST, - component: () => getPage("TicketsAgent"), + component: () => getPage('TicketsAgent'), }, { - path: "tickets/new/:templateId?", - name: "TicketAgentNew", - component: () => getPage("TicketNew"), + path: 'tickets/new/:templateId?', + name: 'TicketAgentNew', + component: () => getPage('TicketNew'), props: true, meta: { - onSuccessRoute: "TicketAgent", - parent: "TicketsAgent", + onSuccessRoute: 'TicketAgent', + parent: 'TicketsAgent', }, }, { - path: "tickets/:ticketId", + path: 'tickets/:ticketId', name: AGENT_PORTAL_TICKET, - component: () => getPage("TicketAgent"), + component: () => getPage('TicketAgent'), props: true, }, { - path: "kb", + path: 'kb', name: AGENT_PORTAL_KNOWLEDGE_BASE, - component: () => import("@/pages/knowledge-base/KnowledgeBase.vue"), + component: () => import('@/pages/knowledge-base/KnowledgeBase.vue'), children: [ { - path: ":categoryId", + path: ':categoryId', name: AGENT_PORTAL_KNOWLEDGE_BASE_CATEGORY, props: true, component: () => - import("@/pages/knowledge-base/KnowledgeBaseCategory.vue"), + import('@/pages/knowledge-base/KnowledgeBaseCategory.vue'), }, { - path: ":categoryId/:subCategoryId", + path: ':categoryId/:subCategoryId', name: AGENT_PORTAL_KNOWLEDGE_BASE_SUB_CATEGORY, props: true, component: () => - import("@/pages/knowledge-base/KnowledgeBaseSubcategory.vue"), + import('@/pages/knowledge-base/KnowledgeBaseSubcategory.vue'), }, ], }, { - path: "kb/articles/:articleId", + path: 'kb/articles/:articleId', name: AGENT_PORTAL_KNOWLEDGE_BASE_ARTICLE, props: true, component: () => - import("@/pages/knowledge-base/KnowledgeBaseArticle.vue"), + import('@/pages/knowledge-base/KnowledgeBaseArticle.vue'), }, { - path: "customers", + path: 'customers', name: AGENT_PORTAL_CUSTOMER_LIST, - component: () => import("@/pages/desk/customer/CustomerList.vue"), + component: () => import('@/pages/desk/customer/CustomerList.vue'), }, { - path: "contacts", + path: 'contacts', name: AGENT_PORTAL_CONTACT_LIST, - component: () => import("@/pages/desk/contact/ContactList.vue"), + component: () => import('@/pages/desk/contact/ContactList.vue'), }, { - path: "agents", + path: 'agents', name: AGENT_PORTAL_AGENT_LIST, - component: () => import("@/pages/desk/agent/AgentList.vue"), + component: () => import('@/pages/desk/agent/AgentList.vue'), }, { - path: "teams", + path: 'teams', name: AGENT_PORTAL_TEAM_LIST, - component: () => import("@/pages/desk/team/TeamList.vue"), + component: () => import('@/pages/desk/team/TeamList.vue'), }, { - path: "teams/:teamId", + path: 'teams/:teamId', name: AGENT_PORTAL_TEAM_SINGLE, - component: () => import("@/pages/desk/team/TeamSingle.vue"), + component: () => import('@/pages/desk/team/TeamSingle.vue'), props: true, }, { - path: "ticket-types", + path: 'ticket-types', name: AGENT_PORTAL_TICKET_TYPE_LIST, - component: () => import("@/pages/desk/ticket_type/TicketTypeList.vue"), + component: () => import('@/pages/desk/ticket_type/TicketTypeList.vue'), }, { - path: "ticket-types/:id", + path: 'ticket-types/:id', name: AGENT_PORTAL_TICKET_TYPE_SINGLE, - component: () => import("@/pages/desk/ticket_type/TicketType.vue"), + component: () => import('@/pages/desk/ticket_type/TicketType.vue'), props: true, }, { - path: "ticket-types/new", + path: 'ticket-types/new', name: AGENT_PORTAL_TICKET_TYPE_NEW, - component: () => import("@/pages/desk/ticket_type/TicketType.vue"), + component: () => import('@/pages/desk/ticket_type/TicketType.vue'), }, { - path: "canned-responses", + path: 'canned-responses', name: AGENT_PORTAL_CANNED_RESPONSE_LIST, component: () => - import("@/pages/desk/canned_response/CannedResponseList.vue"), + import('@/pages/desk/canned_response/CannedResponseList.vue'), }, { - path: "canned-responses/:id", + path: 'canned-responses/:id', name: AGENT_PORTAL_CANNED_RESPONSE_SINGLE, component: () => - import("@/pages/desk/canned_response/CannedResponseSingle.vue"), + import('@/pages/desk/canned_response/CannedResponseSingle.vue'), props: true, }, { - path: "escalation-rules", + path: 'escalation-rules', name: AGENT_PORTAL_ESCALATION_RULE_LIST, component: () => - import("@/pages/desk/escalation/EscalationRuleList.vue"), + import('@/pages/desk/escalation/EscalationRuleList.vue'), }, ], }, ]; export const router = createRouter({ - history: createWebHistory("/helpdesk/"), + history: createWebHistory('/helpdesk/'), routes, }); @@ -198,9 +198,9 @@ router.beforeEach(async (to) => { await initTelemetry(); await authStore.init(); - if ((to.meta.agent && !authStore.hasDeskAccess) || isAuthRoute) { - router.replace({ name: WEBSITE_ROOT }); - } + // if ((to.meta.agent && !authStore.hasDeskAccess) || isAuthRoute) { + // router.replace({ name: WEBSITE_ROOT }); + // } } catch { if (!isAuthRoute) { router.replace({ diff --git a/desk/src/telemetry.ts b/desk/src/telemetry.ts index 9a32f929b..21eb06fdf 100644 --- a/desk/src/telemetry.ts +++ b/desk/src/telemetry.ts @@ -1,57 +1,57 @@ -import { useStorage } from "@vueuse/core"; -import { call } from "frappe-ui"; -import "../../../frappe/frappe/public/js/lib/posthog.js"; +import { useStorage } from '@vueuse/core'; +import { call } from 'frappe-ui'; +import '../../../frappe/frappe/public/js/lib/posthog.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any declare const posthog: any; -const APP = "helpdesk"; +const APP = 'helpdesk'; const SITENAME = window.location.hostname; -const telemetry = useStorage("telemetry", { - enabled: false, - project_id: "", - host: "", +const telemetry = useStorage('telemetry', { + enabled: false, + project_id: '', + host: '', }); export async function init() { - await set_enabled(); - if (!telemetry.value.enabled) return; - try { - await set_credentials(); - posthog.init(telemetry.value.project_id, { - api_host: telemetry.value.host, - autocapture: false, - capture_pageview: false, - capture_pageleave: false, - advanced_disable_decide: true, - }); - posthog.identify(SITENAME); - } catch (e) { - console.trace("Failed to initialize telemetry", e); - telemetry.value.enabled = false; - } + await set_enabled(); + if (!telemetry.value.enabled) return; + try { + await set_credentials(); + posthog.init(telemetry.value.project_id, { + api_host: telemetry.value.host, + autocapture: false, + capture_pageview: false, + capture_pageleave: false, + advanced_disable_decide: true, + }); + posthog.identify(SITENAME); + } catch (e) { + console.trace('Failed to initialize telemetry', e); + telemetry.value.enabled = false; + } } async function set_enabled() { - if (telemetry.value.enabled) return; + if (telemetry.value.enabled) return; - await call("helpdesk.api.telemetry.is_enabled").then((res) => { - telemetry.value.enabled = res; - }); + await call('helpdesk.api.telemetry.is_enabled').then((res) => { + telemetry.value.enabled = res; + }); } async function set_credentials() { - if (!telemetry.value.enabled) return; - if (telemetry.value.project_id && telemetry.value.host) return; + if (!telemetry.value.enabled) return; + if (telemetry.value.project_id && telemetry.value.host) return; - await call("helpdesk.api.telemetry.get_credentials").then((res) => { - telemetry.value.project_id = res.project_id; - telemetry.value.host = res.telemetry_host; - }); + await call('helpdesk.api.telemetry.get_credentials').then((res) => { + telemetry.value.project_id = res.project_id; + telemetry.value.host = res.telemetry_host; + }); } export function capture(event: string) { - if (!telemetry.value.enabled) return; - posthog.capture(`${APP}_${event}`); + if (!telemetry.value.enabled) return; + posthog.capture(`${APP}_${event}`); } diff --git a/helpdesk/api/dashboard.py b/helpdesk/api/dashboard.py index 478011158..c0d07a3ed 100644 --- a/helpdesk/api/dashboard.py +++ b/helpdesk/api/dashboard.py @@ -9,10 +9,9 @@ @frappe.whitelist() -def get_all(): +def get(date_start=None, date_end=None): if not is_agent(): - err = _("You are not allowed to access dashboard") - frappe.throw(err, frappe.PermissionError) + return [] return [ avg_first_response_time(), resolution_within_sla(), diff --git a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py index 1359b3e68..e80bec867 100644 --- a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py +++ b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py @@ -69,12 +69,14 @@ def get_list_select(query: Query): .where(QBCommunication.sent_or_received == "Sent") ) - query = ( - query.select(QBTicket.star) - .select(count_comment) - .select(count_msg_incoming) - .select(count_msg_outgoing) - ) + query = query.select(QBTicket.star) + + if is_agent(): + query = ( + query.select(count_comment) + .select(count_msg_incoming) + .select(count_msg_outgoing) + ) return query From 3dc47b6e58832dab9c216dea9d81fcfd67428c3a Mon Sep 17 00:00:00 2001 From: Sabu Siyad Date: Tue, 21 Nov 2023 12:47:14 +0530 Subject: [PATCH 13/20] chore: update sidebar --- desk/src/assets/logos/helpdesk.png | Bin 0 -> 980 bytes desk/src/components/AppSidebar.vue | 58 ++++++++-------- desk/src/components/SidebarLink.vue | 29 +++----- .../src/components/list-view/LVNavigation.vue | 36 +++++----- desk/src/stores/sidebar.ts | 18 +---- helpdesk/api/dashboard.py | 64 ++++++++++++++++-- 6 files changed, 118 insertions(+), 87 deletions(-) create mode 100644 desk/src/assets/logos/helpdesk.png diff --git a/desk/src/assets/logos/helpdesk.png b/desk/src/assets/logos/helpdesk.png new file mode 100644 index 0000000000000000000000000000000000000000..b7fa2fab9898bb245966dbe38490ccb1e3f77224 GIT binary patch literal 980 zcmeAS@N?(olHy`uVBq!ia0vp^0U*r51|<6gKdl8)oCO|{#S9E$svykh8Km+7D9BhG z?vQfRxa{8g;~}$Y70WS& zj)wAWeWxNWvPAC7xWpD>zPLyv(JagBG(X#Cl_EXgCkvS@A1$)mqg42PLVA$PHc^jH zYMHzCEuF5a;S<1bXd7ta{aar~K;raomilYk^8%GSgMX{O6I}5r$Wo!uUE!qm(NA6h zlQ?oZvbU&xI>&qa*Ot7GJX-qPs`p}|^Q8nO+~Y;P(lof1YjA1za`Y_rf635wD6-B> zs`g!gh*<88>6?Y!#4}}bi|a2594KYiXcj(yqSE2ObvA(F&V> zQMiPQ8>R+mNa4L!f9~7=8=ms-^-PpVZ;X$Zt9yHjr=X1G__<^9zvk-C5~^<9d|=6* ztZMG#=dRt)`J@-1`{5JMoR4K(pSSo+;Ob&lQiI zj;%;m{b}VMYyQM=r^pT8Jt7t+J<5l@8`Iq;ug@HFI+F%!2mV zo+|8`#B%!gCz%yt3WuT_Wg0VuEdBrdb5pzcI_c1nxzE&hWpO#AC>=ITx($qQo^g73v z64FU$isWsQ0~#+$vVFc2zV+tbwSATvvWdy*yp4}tgBNPVG)(HX`1RwdpHl9t3KxII zDJD$2vyZ*wc(QeZ*wM_4xEU^oFTR_1=@y%%_0}D4pU>O!{y^jm_74s(O78hy@RPf| zZN`O39y7mXKasf?^LX3wTz;9C^==!)rta|6<*jLuIk?ksdHI}$Dt*pwQfIRoH@!R@ zYsE6{(Xn2;zp`$<=eADW(3fwb`#idL+vOkp6YBqN)yY>b0cJu5Pgg&ebxsLQ0Gm6X A-T(jq literal 0 HcmV?d00001 diff --git a/desk/src/components/AppSidebar.vue b/desk/src/components/AppSidebar.vue index 15496fc4f..71aa6ff7a 100644 --- a/desk/src/components/AppSidebar.vue +++ b/desk/src/components/AppSidebar.vue @@ -1,18 +1,36 @@ diff --git a/desk/src/pages/desk/contact/ContactList.vue b/desk/src/pages/desk/contact/ContactList.vue index 4240933f3..7e6be3594 100644 --- a/desk/src/pages/desk/contact/ContactList.vue +++ b/desk/src/pages/desk/contact/ContactList.vue @@ -14,12 +14,7 @@ - + diff --git a/desk/src/pages/desk/team/TeamList.vue b/desk/src/pages/desk/team/TeamList.vue index f0e968946..7e6ab0086 100644 --- a/desk/src/pages/desk/team/TeamList.vue +++ b/desk/src/pages/desk/team/TeamList.vue @@ -14,12 +14,7 @@ - + -import { ref } from "vue"; -import { useRouter } from "vue-router"; -import { createResource, usePageMeta, Dialog, FormControl } from "frappe-ui"; -import { isEmpty } from "lodash"; -import { AGENT_PORTAL_TEAM_SINGLE } from "@/router"; -import { createListManager } from "@/composables/listManager"; -import { useError } from "@/composables/error"; -import PageTitle from "@/components/PageTitle.vue"; -import { ListView } from "@/components"; -import IconPlus from "~icons/lucide/plus"; +import { ref } from 'vue'; +import { useRouter } from 'vue-router'; +import { createResource, usePageMeta, Dialog, FormControl } from 'frappe-ui'; +import { isEmpty } from 'lodash'; +import { AGENT_PORTAL_TEAM_SINGLE } from '@/router'; +import { createListManager } from '@/composables/listManager'; +import { useError } from '@/composables/error'; +import PageTitle from '@/components/PageTitle.vue'; +import { ListView } from '@/components'; +import IconPlus from '~icons/lucide/plus'; const router = useRouter(); const showNewDialog = ref(false); const newTeamTitle = ref(null); -const emptyMessage = "No Teams Found"; -const columns = [ - { - label: "Name", - key: "name", - width: "w-80", - }, - { - label: "Assignment rule", - key: "assignment_rule", - width: "w-80", - }, -]; const teams = createListManager({ - doctype: "HD Team", - fields: ["name", "assignment_rule"], + doctype: 'HD Team', + fields: ['name', 'assignment_rule'], auto: true, transform: (data) => { for (const d of data) { @@ -93,17 +75,17 @@ const teams = createListManager({ }); const newTeam = createResource({ - url: "frappe.client.insert", + url: 'frappe.client.insert', makeParams() { return { doc: { - doctype: "HD Team", + doctype: 'HD Team', team_name: newTeamTitle.value, }, }; }, validate(params) { - if (isEmpty(params.doc.team_name)) return "Title is required"; + if (isEmpty(params.doc.team_name)) return 'Title is required'; }, auto: false, onSuccess() { @@ -114,12 +96,12 @@ const newTeam = createResource({ }, }); }, - onError: useError({ title: "Error creating team" }), + onError: useError({ title: 'Error creating team' }), }); usePageMeta(() => { return { - title: "Teams", + title: 'Teams', }; }); diff --git a/desk/src/pages/ticket/TicketAgentSidebar.vue b/desk/src/pages/ticket/TicketAgentSidebar.vue index f408dff05..37baf3515 100644 --- a/desk/src/pages/ticket/TicketAgentSidebar.vue +++ b/desk/src/pages/ticket/TicketAgentSidebar.vue @@ -44,49 +44,49 @@ diff --git a/desk/src/pages/ticket/TicketCommentPublic.vue b/desk/src/pages/ticket/TicketCommentPublic.vue index 858290dc3..c7f76fc0d 100644 --- a/desk/src/pages/ticket/TicketCommentPublic.vue +++ b/desk/src/pages/ticket/TicketCommentPublic.vue @@ -1,5 +1,5 @@