Statistiques de sujets résolus et non résolus avec paramètres de date et de balise

Ce rapport Data Explorer fournit une analyse complète des sujets résolus et non résolus sur un site, dans une plage de dates spécifiée, et éventuellement filtrés par une balise spécifique.

:discourse: Ce rapport nécessite que le plugin Discourse Solved soit activé.

Ce rapport est particulièrement utile pour les administrateurs et les modérateurs qui cherchent à comprendre la réactivité de la communauté et à identifier les domaines à améliorer dans le support et l’engagement des utilisateurs.

Statistiques des sujets résolus et non résolus avec paramètres de date et de balise

--[params]
-- date :start_date = 2022-01-01
-- date :end_date = 2024-01-01
-- text :tag_name = all

WITH valid_topics AS (
    SELECT 
        t.id,
        t.user_id,
        t.title,
        t.views,
        (SELECT COUNT(*) FROM posts WHERE topic_id = t.id AND deleted_at IS NULL AND post_type = 1) - 1 AS "posts_count", 
        t.created_at,
        (CURRENT_DATE::date - t.created_at::date) AS "total_days",
        STRING_AGG(tags.name, ', ') AS tag_names, -- Agréger les balises pour chaque sujet
        c.name AS category_name
    FROM topics t
    LEFT JOIN topic_tags tt ON tt.topic_id = t.id
    LEFT JOIN tags ON tags.id = tt.tag_id
    LEFT JOIN categories c ON c.id = t.category_id
    WHERE t.deleted_at IS NULL
        AND t.created_at::date BETWEEN :start_date AND :end_date
        AND t.archetype = 'regular'
    GROUP BY t.id, c.name
),

solved_topics AS (
    SELECT 
        vt.id,
        dsst.created_at
    FROM discourse_solved_solved_topics dsst
    INNER JOIN valid_topics vt ON vt.id = dsst.topic_id
),

last_reply AS (
    SELECT p.topic_id, p.user_id FROM posts p
    INNER JOIN (SELECT topic_id, MAX(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                GROUP BY topic_id) x ON x.post = p.id
),

first_reply AS (
    SELECT p.topic_id, p.user_id, p.created_at FROM posts p
    INNER JOIN (SELECT topic_id, MIN(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                AND post_number > 1
                GROUP BY topic_id) x ON x.post = p.id
)

SELECT
    CASE 
        WHEN st.id IS NOT NULL THEN 'solved'
        ELSE 'unsolved'
    END AS status,
    vt.tag_names, 
    vt.category_name,
    vt.id AS topic_id,
    vt.user_id AS topic_user_id,
    ue.email,
    vt.title,
    vt.views,
    lr.user_id AS last_reply_user_id,
    ue2.email AS last_reply_user_email,
    vt.created_at::date AS topic_create,
    COALESCE(TO_CHAR(fr.created_at, 'YYYY-MM-DD'), '') AS first_reply_create,
    COALESCE(TO_CHAR(st.created_at, 'YYYY-MM-DD'), '') AS solution_create,
    COALESCE(fr.created_at::date - vt.created_at::date, 0) AS "time_first_reply(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (fr.created_at - vt.created_at)) / 3600.00), 0) AS "time_first_reply(hours)",
    COALESCE(st.created_at::date - vt.created_at::date, 0) AS "time_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.created_at - vt.created_at)) / 3600.00), 0) AS "time_solution(hours)",
    vt.created_at::date,
    vt.posts_count AS number_of_replies,
    vt.total_days AS total_days_without_solution
FROM valid_topics vt
LEFT JOIN last_reply lr ON lr.topic_id = vt.id
LEFT JOIN first_reply fr ON fr.topic_id = vt.id
LEFT JOIN solved_topics st ON st.id = vt.id
INNER JOIN user_emails ue ON vt.user_id = ue.user_id AND ue."primary" = true
LEFT JOIN user_emails ue2 ON lr.user_id = ue2.user_id AND ue2."primary" = true
WHERE (:tag_name = 'all' OR vt.tag_names ILIKE '%' || :tag_name || '%')
GROUP BY st.id, vt.tag_names, vt.category_name, vt.id, vt.user_id, ue.email, vt.title, vt.views, lr.user_id, ue2.email, vt.created_at, fr.created_at, st.created_at, vt.posts_count, vt.total_days
ORDER BY topic_create, vt.total_days DESC

Explication de la requête SQL

La génération du rapport passe par une requête SQL complexe qui utilise des expressions de table communes (CTE) pour organiser et traiter les données efficacement. La requête est structurée comme suit :

  • valid_topics : cette CTE filtre les sujets par la plage de dates spécifiée et l’archétype (‘regular’), en excluant les sujets supprimés. Elle agrège également les balises associées à chaque sujet pour un filtrage ultérieur par nom de balise si spécifié.
  • solved_topics : identifie les sujets qui ont été marqués comme résolus.
  • last_reply : détermine l’utilisateur qui a effectué la dernière réponse sur chaque sujet en trouvant l’ID de publication maximum (indiquant la publication la plus récente) qui n’est pas supprimée et qui est de type de publication 1 (indiquant une publication régulière).
  • first_reply : similaire à last_reply, mais identifie le premier utilisateur à répondre au sujet après la publication originale.

La requête principale combine ensuite ces CTE pour compiler un rapport détaillé sur chaque sujet, y compris s’il est résolu ou non, les noms des balises, le nom de la catégorie, les ID des sujets et des utilisateurs, les e-mails, les vues, le nombre de réponses et les délais pour la première réponse et la solution.

Paramètres

  • start_date : le début de la plage de dates pour laquelle générer le rapport.
  • end_date : la fin de la plage de dates pour laquelle générer le rapport.
  • tag_name : la balise spécifique pour filtrer les sujets. Utilisez ‘all’ pour inclure les sujets avec n’importe quelle balise.

Résultats

Le rapport fournit les informations suivantes pour chaque sujet dans les paramètres spécifiés :

  • status : indique si le sujet a été résolu ou reste non résolu.
  • tag_names : affiche les balises associées au sujet.
  • category_name : affiche la catégorie associée au sujet.
  • topic_id : l’identifiant unique du sujet.
  • topic_user_id : l’ID de l’utilisateur qui a créé le sujet.
  • user_email : l’adresse e-mail du créateur du sujet.
  • title : le titre du sujet.
  • views : le nombre de vues que le sujet a reçues.
  • last_reply_user_id : l’ID de l’utilisateur qui a effectué la dernière réponse sur le sujet.
  • last_reply_user_email : l’adresse e-mail de l’utilisateur qui a effectué la dernière réponse.
  • topic_create : la date de création du sujet.
  • first_reply_create : la date de la première réponse au sujet.
  • solution_create : la date à laquelle une solution a été marquée pour le sujet (le cas échéant).
  • time_first_reply(days/hours) : le temps nécessaire pour recevoir la première réponse, en jours et en heures.
  • time_solution(days/hours) : le temps nécessaire pour résoudre le sujet, en jours et en heures.
  • created_at : la date de création du sujet.
  • number_of_replies : le nombre total de réponses au sujet.
  • total_days_without_solution : le nombre total de jours pendant lesquels le sujet a été actif sans solution.

Exemple de résultats

status tag_names category_name topic_id topic_user_id user_email title views last_reply_user_id last_reply_user_email topic_create first_reply_create solution_create time_first_reply(days) time_first_reply(hours) time_solution(days) time_solution(hours) created_at number_of_replies total_days_without_solution
solved support, password category1 101 1 user1@example.com How to reset my password? 150 3 user3@example.com 2022-01-05 2022-01-06 2022-01-07 1 24 2 48 2022-01-05 5 2
unsolved support, account category2 102 2 user2@example.com Issue with account activation 75 4 user4@example.com 2022-02-10 2022-02-12 2 48 0 0 2022-02-10 3 412
solved support category3 103 5 user5@example.com Can’t upload profile picture 200 6 user6@example.com 2022-03-15 2022-03-16 2022-03-18 1 24 3 72 2022-03-15 8 3
unsolved NULL category4 104 7 user7@example.com Error when posting 50 8 user8@example.com 2022-04-20 0 0 0 0 2022-04-20 0 373
3 « J'aime »

Une autre requête géniale, et une autre demande de ma part. :slight_smile:

Pouvez-vous créer un champ de sélection pour affiner la catégorie/sous-catégorie ?
J’aimerais pouvoir exécuter ce rapport uniquement sur la catégorie de mes tickets.

De plus, j’ai trouvé un cas limite étrange. Vous pourrez peut-être ou non en tenir compte, mais cela ne coûte rien de demander.

J’ai un sujet auquel j’ai répondu et que j’ai marqué comme solution le lendemain de sa publication. Ensuite, un autre technicien a donné une réponse différente et a marqué celle-ci comme solution environ 10 jours plus tard.

Le rapport indique un délai de 1 jour avant la solution, mais un délai total de 10 jours sans solution.

PNG image

Salut @tknospdr,

Pour répondre à vos deux questions ici :

Vous pouvez utiliser la requête ci-dessous pour y répondre :

--[params]
-- date :start_date = 2022-01-01
-- date :end_date = 2024-01-01
-- text :tag_name = all
-- null category_id :category_id

WITH valid_topics AS (
    SELECT 
        t.id,
        t.user_id,
        t.title,
        t.views,
        (SELECT COUNT(*) FROM posts WHERE topic_id = t.id AND deleted_at IS NULL AND post_type = 1) - 1 AS "posts_count", 
        t.created_at,
        (CURRENT_DATE::date - t.created_at::date) AS "total_days",
        STRING_AGG(tags.name, ', ') AS tag_names,
        c.name AS category_name,
        t.category_id
    FROM topics t
    LEFT JOIN topic_tags tt ON tt.topic_id = t.id
    LEFT JOIN tags ON tags.id = tt.tag_id
    LEFT JOIN categories c ON c.id = t.category_id
    WHERE t.deleted_at IS NULL
        AND t.created_at::date BETWEEN :start_date AND :end_date
        AND t.archetype = 'regular'
    GROUP BY t.id, c.name, t.category_id
),

solved_topics AS (
    SELECT 
        dsst.topic_id,
        MIN(dsst.created_at) AS first_solution_at, -- Get earliest solution
        MAX(dsst.created_at) AS latest_solution_at -- Get latest solution
    FROM discourse_solved_solved_topics dsst
    GROUP BY dsst.topic_id
),

last_reply AS (
    SELECT p.topic_id, p.user_id FROM posts p
    INNER JOIN (SELECT topic_id, MAX(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                GROUP BY topic_id) x ON x.post = p.id
),

first_reply AS (
    SELECT p.topic_id, p.user_id, p.created_at FROM posts p
    INNER JOIN (SELECT topic_id, MIN(id) AS post FROM posts p
                WHERE deleted_at IS NULL
                AND post_type = 1
                AND post_number > 1
                GROUP BY topic_id) x ON x.post = p.id
)

SELECT
    CASE 
        WHEN st.topic_id IS NOT NULL THEN 'solved'
        ELSE 'unsolved'
    END AS status,
    vt.tag_names, 
    vt.category_name,
    vt.id AS topic_id,
    vt.user_id AS topic_user_id,
    ue.email,
    vt.title,
    vt.views,
    lr.user_id AS last_reply_user_id,
    ue2.email AS last_reply_user_email,
    vt.created_at::date AS topic_create,
    COALESCE(TO_CHAR(fr.created_at, 'YYYY-MM-DD'), '') AS first_reply_create,
    COALESCE(TO_CHAR(st.first_solution_at, 'YYYY-MM-DD'), '') AS first_solution_create,
    COALESCE(TO_CHAR(st.latest_solution_at, 'YYYY-MM-DD'), '') AS latest_solution_create,
    COALESCE(fr.created_at::date - vt.created_at::date, 0) AS "time_first_reply(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (fr.created_at - vt.created_at)) / 3600.00), 0) AS "time_first_reply(hours)",
    COALESCE(st.first_solution_at::date - vt.created_at::date, 0) AS "time_to_first_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.first_solution_at - vt.created_at)) / 3600.00), 0) AS "time_to_first_solution(hours)",
    COALESCE(st.latest_solution_at::date - vt.created_at::date, 0) AS "time_to_latest_solution(days)",
    COALESCE(CEIL(EXTRACT(EPOCH FROM (st.latest_solution_at - vt.created_at)) / 3600.00), 0) AS "time_to_latest_solution(hours)",
    vt.created_at::date,
    vt.posts_count AS number_of_replies,
    CASE
        WHEN st.topic_id IS NULL THEN vt.total_days
        ELSE COALESCE(st.latest_solution_at::date - vt.created_at::date, 0)
    END AS total_days_without_solution
FROM valid_topics vt
LEFT JOIN last_reply lr ON lr.topic_id = vt.id
LEFT JOIN first_reply fr ON fr.topic_id = vt.id
LEFT JOIN solved_topics st ON st.topic_id = vt.id
INNER JOIN user_emails ue ON vt.user_id = ue.user_id AND ue."primary" = true
LEFT JOIN user_emails ue2 ON lr.user_id = ue2.user_id AND ue2."primary" = true
WHERE (:tag_name = 'all' OR vt.tag_names ILIKE '%' || :tag_name || '%')
  AND (:category_id ISNULL OR vt.category_id = :category_id)
GROUP BY st.topic_id, st.first_solution_at, st.latest_solution_at, vt.tag_names, vt.category_name, vt.id, vt.user_id, ue.email, vt.title, vt.views, lr.user_id, ue2.email, vt.created_at, fr.created_at, vt.posts_count, vt.total_days
ORDER BY topic_create, vt.total_days DESC

Où le paramètre -- null category_id :category_id peut être utilisé pour sélectionner (facultativement) une catégorie pour exécuter le rapport, et les résultats suivent à la fois la première et la dernière solution.

De plus, le résultat total_days_without_solution utilisera désormais la date de la dernière solution au lieu de la première.

1 « J'aime »

Génial, merci ! Ça a l’air super.

1 « J'aime »