Statistiche argomenti risolti e non risolti con parametri data e tag

Questo report di Data Explorer fornisce un’analisi completa degli argomenti risolti e irrisolti su un sito, entro un intervallo di date specificato e opzionalmente filtrato per un tag specifico.

:discourse: Questo report richiede che il plugin Discourse Solved sia abilitato.

Questo report è particolarmente utile per amministratori e moderatori che desiderano comprendere la reattività della community e identificare aree di miglioramento nel supporto e nell’engagement degli utenti.

Statistiche argomenti risolti e irrisolti con parametri data e tag

--[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, -- Aggrega i tag per ogni argomento
        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

Spiegazione della query SQL

La reportistica viene generata tramite una complessa query SQL che utilizza Common Table Expressions (CTE) per organizzare ed elaborare i dati in modo efficiente. La query è strutturata come segue:

  • valid_topics: Questa CTE filtra gli argomenti in base all’intervallo di date specificato e all’archetipo (‘regular’), escludendo gli argomenti eliminati. Aggrega anche i tag associati a ciascun argomento per un successivo filtraggio per nome del tag, se specificato.
  • solved_topics: Identifica gli argomenti contrassegnati come risolti.
  • last_reply: Determina l’utente che ha effettuato l’ultima risposta in ciascun argomento trovando l’ID del post massimo (che indica il post più recente) che non è eliminato ed è di tipo post 1 (indicando un post regolare).
  • first_reply: Simile a last_reply, ma identifica il primo utente a rispondere all’argomento dopo il post originale.

La query principale combina quindi queste CTE per compilare un report dettagliato su ciascun argomento, includendo se è risolto o irrisolto, nomi dei tag, nome della categoria, ID argomento e utente, email, visualizzazioni, conteggio delle risposte e tempistiche per la prima risposta e la soluzione.

Parametri

  • start_date: L’inizio dell’intervallo di date per cui generare il report.
  • end_date: La fine dell’intervallo di date per cui generare il report.
  • tag_name: Il tag specifico per filtrare gli argomenti. Utilizzare ‘all’ per includere argomenti con qualsiasi tag.

Risultati

Il report fornisce le seguenti informazioni per ciascun argomento all’interno dei parametri specificati:

  • status: Indica se l’argomento è stato risolto o rimane irrisolto.
  • tag_names: Mostra i tag associati all’argomento.
  • category_name: Mostra la categoria associata all’argomento.
  • topic_id: L’identificatore univoco dell’argomento.
  • topic_user_id: L’ID dell’utente che ha creato l’argomento.
  • user_email: L’indirizzo email del creatore dell’argomento.
  • title: Il titolo dell’argomento.
  • views: Il numero di visualizzazioni ricevute dall’argomento.
  • last_reply_user_id: L’ID dell’utente che ha effettuato l’ultima risposta all’argomento.
  • last_reply_user_email: L’indirizzo email dell’utente che ha effettuato l’ultima risposta.
  • topic_create: La data di creazione dell’argomento.
  • first_reply_create: La data della prima risposta all’argomento.
  • solution_create: La data in cui è stata contrassegnata una soluzione per l’argomento (se applicabile).
  • time_first_reply(days/hours): Il tempo impiegato per ricevere la prima risposta, in giorni e ore.
  • time_solution(days/hours): Il tempo impiegato per risolvere l’argomento, in giorni e ore.
  • created_at: La data di creazione dell’argomento.
  • number_of_replies: Il numero totale di risposte all’argomento.
  • total_days_without_solution: Il numero totale di giorni in cui l’argomento è stato attivo senza una soluzione.

Risultati di esempio

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 Come reimpostare la mia 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 Problema con l’attivazione dell’account 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 Impossibile caricare l’immagine del profilo 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 Errore durante la pubblicazione 50 8 user8@example.com 2022-04-20 0 0 0 0 2022-04-20 0 373
3 Mi Piace

Un’altra fantastica query e un’altra richiesta da parte mia. :slight_smile:

Puoi creare un campo di selezione per restringere la categoria/sottocategoria?
Mi piacerebbe poter eseguire questo report solo sulla categoria dei miei ticket.

Inoltre, ho trovato un caso limite insolito. Potresti essere in grado o meno di tenerne conto, ma non c’è danno nel chiedere.

Ho un argomento a cui ho risposto e l’ho contrassegnato come soluzione il giorno dopo la sua pubblicazione. Poi un altro tecnico ha dato una risposta diversa e ha contrassegnato quella come soluzione circa 10 giorni dopo.

Il report mostra il tempo alla soluzione come 1 giorno ma il tempo totale senza soluzione come 10 giorni.

PNG image

Ciao @tknospdr,

Per rispondere a entrambe le tue domande qui:

Puoi usare la seguente query per affrontare questo problema:

--[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

Dove il parametro -- null category_id :category_id può essere utilizzato per selezionare (opzionalmente) una categoria per eseguire il report, e i risultati tracciano sia la prima che l’ultima soluzione.

Inoltre, il risultato total_days_without_solution utilizzerà ora la data dell’ultima soluzione invece della prima.

1 Mi Piace

Fantastico, grazie! Sembra ottimo.

1 Mi Piace