Skip to content

Commit

Permalink
refactor: sort (#1107)
Browse files Browse the repository at this point in the history
* feat: ticket priority: add integer value field

* feat: add `sort_options`

* feat: patch: integer values for ticket priorities

* chore: install: change priority integer logic

* fix: sort: remove cache
  • Loading branch information
ssiyad committed Apr 11, 2023
1 parent 0432bae commit 1736fbe
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 82 deletions.
28 changes: 8 additions & 20 deletions desk/src/components/desk/tickets/PresetFilters.vue
Original file line number Diff line number Diff line change
@@ -1,34 +1,22 @@
<template>
<Dropdown placement="left" :options="options">
<template #default="{ toggleDropdown }">
<div
class="flex select-none items-center gap-2 rounded-lg border border-gray-300 px-2 py-1"
:class="{ 'cursor-pointer': !$_.isEmpty(options) }"
@click="toggleDropdown"
>
<div class="text-base">
{{ title }}
</div>
<FeatherIcon
v-if="!$_.isEmpty(options)"
name="chevron-down"
class="h-4 w-4 stroke-2"
/>
</div>
</template>
</Dropdown>
<Dropdown
:options="options"
:button="{
label: title,
'icon-right': 'chevron-down',
}"
/>
</template>

<script>
import { ref } from "vue";
import { Dropdown, FeatherIcon } from "frappe-ui";
import { Dropdown } from "frappe-ui";
import { useListFilters } from "@/composables/listFilters";
export default {
name: "PresetFilters",
components: {
Dropdown,
FeatherIcon,
},
setup() {
const listFilters = useListFilters();
Expand Down
6 changes: 2 additions & 4 deletions desk/src/composables/listFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,9 @@ export function useListFilters() {
}

function queryOrderBy() {
const { sortBy, sortDirection = "desc" } = route.query;

const { sortBy, sortDirection } = route.query;
if (!sortBy) return;

return `${sortBy} ${sortDirection}`;
return [sortBy, sortDirection].join(" ").trim();
}

function applyQuery(query: Array<FilterItem> | string) {
Expand Down
5 changes: 3 additions & 2 deletions desk/src/composables/listManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export function createListManager(options: ListOptions) {
pageLength: pageLength.value,
start: start.value,
cache,
auto: false,
onSuccess() {
meta.submit({
doctype,
Expand Down Expand Up @@ -84,10 +85,10 @@ export function createListManager(options: ListOptions) {

onMounted(() => {
const queryFilters = filterManager.queryFilters();
const sortBy = filterManager.queryOrderBy();
const orderByQuery = filterManager.queryOrderBy();

if (!isEmpty(queryFilters)) filters.value = queryFilters;
if (sortBy) orderBy.value = sortBy;
if (orderByQuery) orderBy.value = orderByQuery;

list.reload();
});
Expand Down
71 changes: 29 additions & 42 deletions desk/src/pages/desk/Tickets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,41 +16,38 @@
:button="{
label: 'Status',
iconRight: 'chevron-down',
class: 'text-gray-500 bg-gray-200 rounded-lg',
}"
/>
<Dropdown
:options="filterByPriorityOptions"
:button="{
label: 'Priority',
iconRight: 'chevron-down',
class: 'text-gray-500 bg-gray-200 rounded-lg',
}"
/>
</div>
<div class="flex items-center gap-2">
<CompositeFilters />
<Dropdown
:options="sortDropdownOptions"
:options="sortOptions"
:button="{
label: 'Sort',
iconLeft: 'list',
class: 'text-gray-500 bg-gray-200 rounded-lg',
}"
>
<template #default>
<Button>
<template #icon-left>
<IconSort />
<IconSort class="mr-1.5 h-4 w-4" />
</template>
<div class="px-1">Sort</div>
<div>Sort</div>
</Button>
</template>
</Dropdown>
</div>
</div>
<div class="bg-gray-100 px-6 font-sans text-base text-gray-500">
<div class="flex items-center gap-2 px-2 py-1">
<div class="flex items-center gap-2 px-2 py-1.5">
<div class="pl-1 pr-4">
<Input
type="checkbox"
Expand Down Expand Up @@ -111,7 +108,7 @@
<Dropdown :options="priorityDropdownOptions(t.name, t.priority)">
<template #default>
<Badge
:color-map="priorityColorMap"
:color-map="ticketPriorityStore.colorMap"
:label="t.priority"
class="cursor-pointer"
/>
Expand Down Expand Up @@ -245,57 +242,38 @@ export default {
},
inject: ["agents"],
setup() {
const selected = ref(new Set());
const authStore = useAuthStore();
const listFilters = useListFilters();
const ticketStatusStore = useTicketStatusStore();
const selected = ref(new Set());
const showNewTicketDialog = ref(false);
const ticketPriorityStore = useTicketPriorityStore();
const authStore = useAuthStore();
const ticketStatusStore = useTicketStatusStore();
const ticketList = createListManager({
doctype: "HD Ticket",
pageLength: 20,
orderBy: "modified desc",
});
return {
authStore,
listFilters,
selected,
showNewTicketDialog,
ticketList,
ticketPriorityStore,
ticketStatusStore,
};
},
data() {
function sortBy(key, dir) {
this.$router.push({
query: { q: this.$route.query.q, sortBy: key, sortDirection: dir },
});
}
const sortOptions = [
{ label: "HD Ticket Type", value: "ticket_type" },
{ label: "Modified", value: "modified" },
{ label: "Created", value: "created" },
];
const sortDropdownOptions = sortOptions.map((o) => ({
label: o.label,
handler: sortBy.bind(this, o.value, o.sortDirection || "desc"),
}));
return {
priorityColorMap: {
Urgent: "red",
High: "yellow",
Medium: "green",
Low: "blue",
},
showNewTicketDialog: false,
sortDropdownOptions,
};
},
computed: {
sortOptions() {
const options = this.$resources.sortOptions.data || [];
return options.map((o) => ({
label: o,
value: o,
handler: () =>
this.$router.push({ query: { ...this.$route.query, sortBy: o } }),
}));
},
allSelected() {
if (this.$_.isEmpty(this.ticketList.list.data)) return;
return this.ticketList.list.data.length === this.selected.size;
Expand Down Expand Up @@ -417,10 +395,19 @@ export default {
value,
},
];
this.listFilters.applyQuery(this.listFilters.toQuery(f));
this.listFilters.applyQuery(f);
},
},
resources: {
sortOptions() {
return {
url: "helpdesk.extends.doc.sort_options",
auto: true,
params: {
doctype: "HD Ticket",
},
};
},
bulkAssignTicketStatus() {
return {
url: "helpdesk.api.ticket.bulk_assign_ticket_status",
Expand Down
10 changes: 9 additions & 1 deletion desk/src/stores/ticketPriority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@ type TicketPriority = {
export const useTicketPriorityStore = defineStore("ticketPriority", () => {
const d__ = createListResource({
doctype: "HD Ticket Priority",
orderBy: "integer_value desc",
auto: true,
});

const options: ComputedRef<Array<TicketPriority>> = computed(
() => d__.list?.data || []
);
const names = computed(() => options.value.map((o) => o.name));
const colorMap = {
Urgent: "red",
High: "yellow",
Medium: "green",
Low: "blue",
};

return {
options,
colorMap,
names,
options,
};
});
5 changes: 3 additions & 2 deletions helpdesk/extends/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from frappe.query_builder import Query
from frappe.query_builder.functions import Count

from .doc import apply_sort
from .qb import get_query


Expand All @@ -30,14 +31,14 @@ def get_list(
table=doctype,
fields=fields,
filters=filters,
order_by=order_by,
offset=start,
limit=limit,
group_by=group_by,
)

query = apply_custom_filters(doctype, query)
query = apply_hook(doctype, query)
query = apply_sort(doctype, order_by, query)

return query.run(as_dict=True, debug=debug)

Expand All @@ -58,12 +59,12 @@ def get_list_meta(
query: Query = get_query(
table=doctype,
filters=filters,
order_by=order_by,
group_by=group_by,
)

query = apply_custom_filters(doctype, query)
query = apply_hook(doctype, query)
query = apply_sort(doctype, order_by, query)

total_count = Count("*").as_("total_count")
query = query.select(total_count)
Expand Down
40 changes: 40 additions & 0 deletions helpdesk/extends/doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from types import FunctionType

import frappe
from frappe.model.document import get_controller
from frappe.query_builder import Order, Query

SORT_OPTIONS_METHOD = "sort_options"
DEFAULT_SORT_FIELD = "modified"
DEFAULT_SORT_DIRECTION = Order.desc


@frappe.whitelist()
def sort_options(doctype: str):
c = get_controller(doctype)

if not hasattr(c, SORT_OPTIONS_METHOD):
return []

return c.sort_options().keys()


def apply_sort(doctype: str, order_by: str, query: Query):
controller = get_controller(doctype)
fallback = query.orderby(DEFAULT_SORT_FIELD, order=DEFAULT_SORT_DIRECTION)

if not hasattr(controller, SORT_OPTIONS_METHOD):
return fallback

action = controller.sort_options().get(order_by)

if isinstance(action, FunctionType):
return action(query)

if isinstance(action, (list, tuple)):
return query.orderby(action[0], order=action[1])

if isinstance(action, str):
return query.orderby(action, order=DEFAULT_SORT_DIRECTION)

return fallback
31 changes: 29 additions & 2 deletions helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
from datetime import timedelta
from typing import List
from functools import lru_cache

import frappe
from frappe import _
Expand All @@ -13,7 +14,7 @@
from frappe.email.inbox import link_communication_to_document
from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from frappe.query_builder import Case, DocType
from frappe.query_builder import Case, DocType, Order
from frappe.query_builder.functions import Count
from frappe.utils import date_diff, get_datetime, now_datetime, time_diff_in_seconds
from frappe.utils.user import is_website_user
Expand Down Expand Up @@ -89,6 +90,30 @@ def can_ignore_restrictions(user: str) -> bool:
# Must be part of at-least one team which can ignore restrictions
return len(teams) > 0

@staticmethod
@lru_cache
def sort_options():
def by_priority(query: Query, direction: Order):
QBTicket = frappe.qb.DocType("HD Ticket")
QBPriority = frappe.qb.DocType("HD Ticket Priority")

query = (
query.left_join(QBPriority)
.on(QBPriority.name == QBTicket.priority)
.orderby(QBPriority.integer_value, order=direction)
.orderby(QBTicket.resolution_by, order=Order.desc)
)

return query

return {
"Due date": ("resolution_by", Order.asc),
"Created on": ("creation", Order.asc),
"High to low priority": lambda q: by_priority(q, Order.asc),
"Low to high priority": lambda q: by_priority(q, Order.desc),
"Last modified on": "modified",
}

def autoname(self):
return self.name

Expand Down Expand Up @@ -271,7 +296,9 @@ def assign_agent(self, agent):
agent_name = frappe.get_value("HD Agent", agent, "agent_name")
log_ticket_activity(self.name, f"assigned to {agent_name}")
frappe.publish_realtime(
"helpdesk:update-ticket-assignee", {"ticket_id": self.name}, after_commit=True
"helpdesk:update-ticket-assignee",
{"ticket_id": self.name},
after_commit=True,
)

def get_assigned_agent(self):
Expand Down
Loading

0 comments on commit 1736fbe

Please sign in to comment.