mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-09-10 22:25:39 +00:00
aa9e4cdae2
- Introduced a new category field for conversation statuses to classify them as 'open', 'waiting', or 'resolved'. Which allows max assignment limits in team to work with custom statuses as well.
363 lines
9.6 KiB
SQL
363 lines
9.6 KiB
SQL
-- name: get-overview-counts
|
|
SELECT
|
|
json_build_object(
|
|
'open',
|
|
COUNT(*),
|
|
'awaiting_response',
|
|
COUNT(
|
|
CASE
|
|
WHEN c.last_message_sender = 'contact' THEN 1
|
|
END
|
|
),
|
|
'unassigned',
|
|
COUNT(
|
|
CASE
|
|
WHEN c.assigned_user_id IS NULL THEN 1
|
|
END
|
|
),
|
|
'pending',
|
|
COUNT(
|
|
CASE
|
|
WHEN c.first_reply_at IS NULL THEN 1
|
|
END
|
|
),
|
|
'agents_online',
|
|
(
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
users
|
|
WHERE
|
|
availability_status = 'online'
|
|
AND type = 'agent'
|
|
AND deleted_at is null
|
|
),
|
|
'agents_away',
|
|
(
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
users
|
|
WHERE
|
|
availability_status = 'away_manual'
|
|
AND type = 'agent'
|
|
AND deleted_at is null
|
|
),
|
|
'agents_reassigning',
|
|
(
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
users
|
|
WHERE
|
|
availability_status = 'away_and_reassigning'
|
|
AND type = 'agent'
|
|
AND deleted_at is null
|
|
),
|
|
'agents_offline',
|
|
(
|
|
SELECT
|
|
COUNT(*)
|
|
FROM
|
|
users
|
|
WHERE
|
|
availability_status = 'offline'
|
|
AND type = 'agent'
|
|
AND deleted_at is null
|
|
)
|
|
)
|
|
FROM
|
|
conversations c
|
|
INNER JOIN conversation_statuses s ON c.status_id = s.id
|
|
WHERE
|
|
s.category != 'resolved';
|
|
|
|
-- name: get-overview-sla-counts
|
|
WITH first_and_resolution AS (
|
|
SELECT
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
first_response_met_at IS NOT NULL
|
|
) AS first_response_met_count,
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
first_response_breached_at IS NOT NULL
|
|
) AS first_response_breached_count,
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
resolution_met_at IS NOT NULL
|
|
) AS resolution_met_count,
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
resolution_breached_at IS NOT NULL
|
|
) AS resolution_breached_count,
|
|
COALESCE(
|
|
AVG(
|
|
EXTRACT(
|
|
EPOCH
|
|
FROM
|
|
(first_response_met_at - created_at)
|
|
)
|
|
) FILTER (
|
|
WHERE
|
|
first_response_met_at IS NOT NULL
|
|
),
|
|
0
|
|
) AS avg_first_response_time_sec,
|
|
COALESCE(
|
|
AVG(
|
|
EXTRACT(
|
|
EPOCH
|
|
FROM
|
|
(resolution_met_at - created_at)
|
|
)
|
|
) FILTER (
|
|
WHERE
|
|
resolution_met_at IS NOT NULL
|
|
),
|
|
0
|
|
) AS avg_resolution_time_sec
|
|
FROM
|
|
applied_slas
|
|
WHERE
|
|
created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
),
|
|
next_response AS (
|
|
SELECT
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
met_at IS NOT NULL
|
|
) AS next_response_met_count,
|
|
COUNT(*) FILTER (
|
|
WHERE
|
|
breached_at IS NOT NULL
|
|
) AS next_response_breached_count,
|
|
COALESCE(
|
|
AVG(
|
|
EXTRACT(
|
|
EPOCH
|
|
FROM
|
|
(met_at - created_at)
|
|
)
|
|
) FILTER (
|
|
WHERE
|
|
met_at IS NOT NULL
|
|
),
|
|
0
|
|
) AS avg_next_response_time_sec
|
|
FROM
|
|
sla_events
|
|
WHERE
|
|
created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
AND type = 'next_response'
|
|
)
|
|
SELECT
|
|
fas.first_response_met_count,
|
|
fas.first_response_breached_count,
|
|
fas.avg_first_response_time_sec,
|
|
nr.next_response_met_count,
|
|
nr.next_response_breached_count,
|
|
nr.avg_next_response_time_sec,
|
|
fas.resolution_met_count,
|
|
fas.resolution_breached_count,
|
|
fas.avg_resolution_time_sec,
|
|
CASE
|
|
WHEN (fas.first_response_met_count + fas.first_response_breached_count) > 0
|
|
THEN ROUND((fas.first_response_met_count::numeric / (fas.first_response_met_count + fas.first_response_breached_count)::numeric) * 100, 1)
|
|
ELSE 0
|
|
END AS first_response_compliance_percent,
|
|
CASE
|
|
WHEN (nr.next_response_met_count + nr.next_response_breached_count) > 0
|
|
THEN ROUND((nr.next_response_met_count::numeric / (nr.next_response_met_count + nr.next_response_breached_count)::numeric) * 100, 1)
|
|
ELSE 0
|
|
END AS next_response_compliance_percent,
|
|
CASE
|
|
WHEN (fas.resolution_met_count + fas.resolution_breached_count) > 0
|
|
THEN ROUND((fas.resolution_met_count::numeric / (fas.resolution_met_count + fas.resolution_breached_count)::numeric) * 100, 1)
|
|
ELSE 0
|
|
END AS resolution_compliance_percent
|
|
FROM
|
|
first_and_resolution fas,
|
|
next_response nr;
|
|
|
|
-- name: get-overview-charts
|
|
WITH new_conversations AS (
|
|
SELECT
|
|
json_agg(row_to_json(agg)) AS data
|
|
FROM
|
|
(
|
|
SELECT
|
|
TO_CHAR(created_at :: date, 'YYYY-MM-DD') AS date,
|
|
COUNT(*) AS count
|
|
FROM
|
|
conversations c
|
|
WHERE
|
|
c.created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
GROUP BY
|
|
date
|
|
ORDER BY
|
|
date
|
|
) agg
|
|
),
|
|
resolved_conversations AS (
|
|
SELECT
|
|
json_agg(row_to_json(agg)) AS data
|
|
FROM
|
|
(
|
|
SELECT
|
|
TO_CHAR(resolved_at :: date, 'YYYY-MM-DD') AS date,
|
|
COUNT(*) AS count
|
|
FROM
|
|
conversations c
|
|
WHERE
|
|
c.resolved_at IS NOT NULL
|
|
AND c.created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
GROUP BY
|
|
date
|
|
ORDER BY
|
|
date
|
|
) agg
|
|
)
|
|
SELECT
|
|
json_build_object(
|
|
'new_conversations',
|
|
(
|
|
SELECT
|
|
data
|
|
FROM
|
|
new_conversations
|
|
),
|
|
'resolved_conversations',
|
|
(
|
|
SELECT
|
|
data
|
|
FROM
|
|
resolved_conversations
|
|
)
|
|
) AS result;
|
|
|
|
-- name: get-overview-csat
|
|
SELECT
|
|
json_build_object(
|
|
'average_rating',
|
|
COALESCE(AVG(rating) FILTER (WHERE rating > 0), 0),
|
|
'total_responses',
|
|
COUNT(*) FILTER (WHERE rating > 0),
|
|
'total_sent',
|
|
COUNT(*),
|
|
'response_rate',
|
|
CASE
|
|
WHEN COUNT(*) > 0
|
|
THEN ROUND((COUNT(*) FILTER (WHERE rating > 0)::numeric / COUNT(*)::numeric) * 100, 1)
|
|
ELSE 0
|
|
END
|
|
) AS result
|
|
FROM
|
|
csat_responses
|
|
WHERE
|
|
created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END;
|
|
|
|
-- name: get-overview-message-volume
|
|
WITH stats AS (
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(*) FILTER (WHERE type = 'incoming') AS incoming,
|
|
COUNT(*) FILTER (WHERE type = 'outgoing') AS outgoing,
|
|
COUNT(DISTINCT conversation_id) AS convos
|
|
FROM
|
|
conversation_messages
|
|
WHERE
|
|
type IN ('incoming', 'outgoing')
|
|
AND created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
)
|
|
SELECT
|
|
json_build_object(
|
|
'total_messages', total,
|
|
'incoming_messages', incoming,
|
|
'outgoing_messages', outgoing,
|
|
'messages_per_conversation',
|
|
CASE
|
|
WHEN convos > 0 THEN ROUND(total::numeric / convos::numeric, 1)
|
|
ELSE 0
|
|
END
|
|
) AS result
|
|
FROM
|
|
stats;
|
|
|
|
-- name: get-overview-tag-distribution
|
|
WITH tag_counts AS (
|
|
SELECT
|
|
t.id AS tag_id,
|
|
t.name AS tag_name,
|
|
COUNT(ct.conversation_id) AS count
|
|
FROM
|
|
tags t
|
|
LEFT JOIN conversation_tags ct ON t.id = ct.tag_id
|
|
LEFT JOIN conversations c ON ct.conversation_id = c.id
|
|
WHERE
|
|
c.created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
OR c.id IS NULL
|
|
GROUP BY
|
|
t.id, t.name
|
|
ORDER BY
|
|
count DESC
|
|
LIMIT 10
|
|
),
|
|
tagging AS (
|
|
SELECT
|
|
COUNT(DISTINCT c.id) FILTER (
|
|
WHERE EXISTS (
|
|
SELECT 1 FROM conversation_tags ct
|
|
WHERE ct.conversation_id = c.id
|
|
)
|
|
) AS tagged,
|
|
COUNT(DISTINCT c.id) FILTER (
|
|
WHERE NOT EXISTS (
|
|
SELECT 1 FROM conversation_tags ct
|
|
WHERE ct.conversation_id = c.id
|
|
)
|
|
) AS untagged
|
|
FROM
|
|
conversations c
|
|
WHERE
|
|
c.created_at >= CASE
|
|
WHEN %d = 0 THEN CURRENT_DATE
|
|
ELSE NOW() - INTERVAL '%d days'
|
|
END
|
|
)
|
|
SELECT
|
|
json_build_object(
|
|
'top_tags',
|
|
COALESCE((SELECT json_agg(row_to_json(tc)) FROM tag_counts tc), '[]'::json),
|
|
'tagged_conversations', tagged,
|
|
'untagged_conversations', untagged,
|
|
'tagged_percentage',
|
|
CASE
|
|
WHEN (tagged + untagged) > 0
|
|
THEN ROUND((tagged::numeric / (tagged + untagged)::numeric) * 100, 1)
|
|
ELSE 0
|
|
END
|
|
) AS result
|
|
FROM
|
|
tagging; |