Esempi di utilizzo
Questa sezione raccoglie esempi pratici di integrazione di Askme Desk all’interno di applicazioni e script esterni, utilizzando le API REST esposte dal sistema tramite il prefisso /rest.
Gli esempi mostrano scenari ricorrenti di utilizzo, tra cui:
- autenticazione OAuth 2.0 tramite Keycloak
- creazione e gestione di richieste di I livello
- creazione e avanzamento di ticket di II livello
- interrogazione di statistiche SLA
- importazione massiva di richieste da file CSV
Esempio 1: client Python per richieste e ticket
Il seguente esempio mostra un client Python per l’invocazione delle principali API REST di Askme Desk, con gestione automatica del token OAuth 2.0 ottenuto tramite client credentials flow.
import requests
from datetime import datetime, timedelta
from typing import Optional
class DeskClient:
"""Client per le API REST di Askme Desk con gestione automatica del token OAuth 2.0."""
def __init__(self, base_url: str, client_id: str, client_secret: str, keycloak_url: str):
self.base_url = base_url.rstrip("/") # es. https://desk.example.com/rest
self.client_id = client_id
self.client_secret = client_secret
self.keycloak_url = keycloak_url
self.access_token: Optional[str] = None
self.token_expiry: Optional[datetime] = None
def _get_token(self) -> str:
"""Ottiene un nuovo token OAuth 2.0 da Keycloak."""
url = f"{self.keycloak_url}/protocol/openid-connect/token"
payload = {
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret,
}
response = requests.post(url, data=payload)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
self.token_expiry = datetime.utcnow() + timedelta(seconds=data["expires_in"] - 60)
return self.access_token
def _headers(self) -> dict:
"""Restituisce gli header HTTP con un token valido."""
if self.access_token is None or datetime.utcnow() >= self.token_expiry:
self._get_token()
return {
"Authorization": f"Bearer {self.access_token}",
"Content-Type": "application/json",
}
# ── Richieste (I livello) ──
def crea_richiesta(self, id_servizio: int, id_tipo_richiesta: int,
oggetto: str, descrizione: str = "", **kwargs) -> dict:
"""Crea una nuova richiesta di I livello."""
payload = {
"idServizio": id_servizio,
"idTipoRichiesta": id_tipo_richiesta,
"oggetto": oggetto,
"descrizione": descrizione,
**kwargs,
}
resp = requests.post(
f"{self.base_url}/richieste/creazione-richiesta",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def lista_richieste(self, filtri: dict = None) -> dict:
"""Recupera l’elenco delle richieste con filtri opzionali."""
resp = requests.get(
f"{self.base_url}/richieste/",
json=filtri or {},
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def dettaglio_richiesta(self, id_richiesta: int) -> dict:
"""Recupera il dettaglio completo di una richiesta."""
resp = requests.get(
f"{self.base_url}/richieste/{id_richiesta}",
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def assegna_richiesta(self, id_richiesta: int,
id_assegnatore: int, id_assegnatario: int,
note: str = "") -> dict:
"""Assegna una richiesta a un operatore."""
payload = {
"idRichiesta": id_richiesta,
"idUtenteAssegnatore": id_assegnatore,
"idUtenteAssegnatario": id_assegnatario,
"noteAss": note,
}
resp = requests.post(
f"{self.base_url}/richieste/assegnazione-richiesta",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def presa_in_carico(self, id_richiesta: int, id_utente: int,
note: str = "") -> dict:
"""Esegue la presa in carico di una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"idUtente": id_utente,
"note": note,
}
resp = requests.post(
f"{self.base_url}/richieste/presa-in-carico",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def chiudi_richiesta(self, id_richiesta: int, id_utente: int,
note: str = "") -> dict:
"""Chiude una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"idUtente": id_utente,
"note": note,
}
resp = requests.post(
f"{self.base_url}/richieste/chiusura",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
# ── Ticket (II livello) ──
def crea_ticket(self, id_richiesta: int, oggetto: str,
id_priorita: int = None, **kwargs) -> dict:
"""Crea un ticket di II livello associato a una richiesta."""
payload = {
"idRichiesta": id_richiesta,
"oggetto": oggetto,
**kwargs,
}
if id_priorita:
payload["idPriorita"] = id_priorita
resp = requests.post(
f"{self.base_url}/ticket/creazione-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def dettaglio_ticket(self, id_ticket: int) -> dict:
"""Recupera il dettaglio completo di un ticket."""
resp = requests.get(
f"{self.base_url}/ticket/{id_ticket}",
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def avanza_ticket(self, id_ticket: int, id_azione: int,
note: str = "", durata: int = 0) -> dict:
"""Avanza un ticket allo step successivo del workflow."""
payload = {
"idTicket": id_ticket,
"idAzione": id_azione,
"note": note,
"durata": durata,
}
resp = requests.post(
f"{self.base_url}/ticket/avanzamento-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
def assegna_ticket(self, id_ticket: int,
id_assegnatore: int, id_assegnatario: int,
note: str = "") -> dict:
"""Assegna un ticket a un operatore di II livello."""
payload = {
"idTicket": id_ticket,
"idUtenteAssegnatore": id_assegnatore,
"idUtenteAssegnatario": id_assegnatario,
"noteAss": note,
}
resp = requests.post(
f"{self.base_url}/ticket/assegnazione-ticket",
json=payload,
headers=self._headers(),
)
resp.raise_for_status()
return resp.json()
# ── Esempio di utilizzo ──
client = DeskClient(
base_url="https://desk.example.com/rest",
client_id="my-integration",
client_secret="secret",
keycloak_url="https://keycloak.example.com/realms/askme",
)
# 1. Creazione di una richiesta di I livello
info = client.crea_richiesta(
id_servizio=1,
id_tipo_richiesta=5,
oggetto="Problema di login",
descrizione="L'utente non riesce ad accedere al portale aziendale",
idUrgenza=2,
codiceCanale="WEB",
)
print(f"Richiesta creata: ID {info['idRichiesta']}")
# 2. Recupero del dettaglio
dettaglio = client.dettaglio_richiesta(info["idRichiesta"])
print(f"Oggetto: {dettaglio['oggetto']}")
print(f"Stato: {dettaglio['statoRichiesta']}")
print(f"Servizio: {dettaglio['servizio']['nome']}")
# 3. Assegnazione della richiesta
ris = client.assegna_richiesta(
id_richiesta=info["idRichiesta"],
id_assegnatore=100,
id_assegnatario=200,
note="Assegnata per competenza tecnica",
)
print(f"Assegnazione: {ris['esitoAssegnazione']}")
# 4. Presa in carico
ris = client.presa_in_carico(
id_richiesta=info["idRichiesta"],
id_utente=200,
)
print(f"Presa in carico: {ris['esitoPresaInCarico']}")
# 5. Creazione di un ticket di II livello
ticket = client.crea_ticket(
id_richiesta=info["idRichiesta"],
oggetto="Analisi tecnica - Problema login",
id_priorita=3,
descrizione="Approfondimento tecnico richiesto",
)
id_ticket = ticket["ticket"]["idTicket"]
print(f"Ticket creato: {ticket['ticket']['protocollo']}")
# 6. Avanzamento del ticket nel workflow
ris = client.avanza_ticket(
id_ticket=id_ticket,
id_azione=10,
note="Analisi completata, problema risolto",
durata=120,
)
print(f"Ticket avanzato allo step {ris['idStep']}")
# 7. Chiusura della richiesta
ris = client.chiudi_richiesta(
id_richiesta=info["idRichiesta"],
id_utente=200,
note="Problema risolto con reset credenziali",
)
print(f"Chiusura: {ris['esitoChiusuraRichiesta']}")
Descrizione del flusso
L’esempio mostra un flusso applicativo completo:
- autenticazione automatica tramite OAuth 2.0
- creazione di una richiesta di I livello
- consultazione del dettaglio della richiesta
- assegnazione a un operatore
- presa in carico
- creazione di un ticket collegato
- avanzamento del ticket nel workflow
- chiusura della richiesta
Esempio 2: client JavaScript / Node.js
L’esempio seguente presenta un client JavaScript per ambiente Node.js basato su axios, con gestione del token OAuth 2.0 e invocazione delle API principali di richieste e ticket.
const axios = require("axios");
class DeskClient {
constructor({ baseUrl, clientId, clientSecret, keycloakUrl }) {
this.baseUrl = baseUrl.replace(/\/$/, "");
this.clientId = clientId;
this.clientSecret = clientSecret;
this.keycloakUrl = keycloakUrl;
this.accessToken = null;
this.tokenExpiry = null;
}
async _getToken() {
const url = `${this.keycloakUrl}/protocol/openid-connect/token`;
const params = new URLSearchParams({
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
});
const { data } = await axios.post(url, params.toString(), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
this.accessToken = data.access_token;
this.tokenExpiry = Date.now() + (data.expires_in - 60) * 1000;
return this.accessToken;
}
async _headers() {
if (!this.accessToken || Date.now() >= this.tokenExpiry) {
await this._getToken();
}
return {
Authorization: `Bearer ${this.accessToken}`,
"Content-Type": "application/json",
};
}
// ── Richieste ──
async creaRichiesta(payload) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/creazione-richiesta`,
payload,
{ headers }
);
return data;
}
async listaRichieste(filtri = {}) {
const headers = await this._headers();
const { data } = await axios.get(`${this.baseUrl}/richieste/`, {
headers,
data: filtri,
});
return data;
}
async dettaglioRichiesta(idRichiesta) {
const headers = await this._headers();
const { data } = await axios.get(
`${this.baseUrl}/richieste/${idRichiesta}`,
{ headers }
);
return data;
}
async assegnaRichiesta(idRichiesta, idAssegnatore, idAssegnatario, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/assegnazione-richiesta`,
{
idRichiesta,
idUtenteAssegnatore: idAssegnatore,
idUtenteAssegnatario: idAssegnatario,
noteAss: note
},
{ headers }
);
return data;
}
async presaInCarico(idRichiesta, idUtente, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/presa-in-carico`,
{ idRichiesta, idUtente, note },
{ headers }
);
return data;
}
async chiudiRichiesta(idRichiesta, idUtente, note = "") {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/richieste/chiusura`,
{ idRichiesta, idUtente, note },
{ headers }
);
return data;
}
// ── Ticket ──
async creaTicket(payload) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/ticket/creazione-ticket`,
payload,
{ headers }
);
return data;
}
async dettaglioTicket(idTicket) {
const headers = await this._headers();
const { data } = await axios.get(
`${this.baseUrl}/ticket/${idTicket}`,
{ headers }
);
return data;
}
async avanzaTicket(idTicket, idAzione, note = "", durata = 0) {
const headers = await this._headers();
const { data } = await axios.post(
`${this.baseUrl}/ticket/avanzamento-ticket`,
{ idTicket, idAzione, note, durata },
{ headers }
);
return data;
}
}
// ── Esempio di utilizzo ──
(async () => {
const client = new DeskClient({
baseUrl: "https://desk.example.com/rest",
clientId: "my-integration",
clientSecret: "secret",
keycloakUrl: "https://keycloak.example.com/realms/askme",
});
try {
// 1. Creazione richiesta
const info = await client.creaRichiesta({
idServizio: 1,
idTipoRichiesta: 5,
oggetto: "Problema di login",
descrizione: "L'utente non riesce ad accedere al portale",
idUrgenza: 2,
codiceCanale: "WEB",
});
console.log(`Richiesta creata: ${info.idRichiesta}`);
// 2. Recupero dettaglio richiesta
const dettaglio = await client.dettaglioRichiesta(info.idRichiesta);
console.log(`Stato: ${dettaglio.statoRichiesta}`);
console.log(`Servizio: ${dettaglio.servizio.nome}`);
// 3. Presa in carico
const pc = await client.presaInCarico(info.idRichiesta, 200);
console.log(`Presa in carico: ${pc.esitoPresaInCarico}`);
// 4. Creazione ticket di II livello
const ticket = await client.creaTicket({
idRichiesta: info.idRichiesta,
oggetto: "Analisi tecnica - Problema login",
idPriorita: 3,
});
console.log(`Ticket: ${ticket.ticket.protocollo}`);
// 5. Avanzamento ticket
const av = await client.avanzaTicket(
ticket.ticket.idTicket,
10,
"Analisi completata",
60
);
console.log(`Step: ${av.idStep}`);
// 6. Chiusura richiesta
const ch = await client.chiudiRichiesta(
info.idRichiesta,
200,
"Risolto"
);
console.log(`Chiusura: ${ch.esitoChiusuraRichiesta}`);
} catch (error) {
console.error("Errore:", error.response?.data || error.message);
}
})();
Descrizione del flusso
Anche in questo caso viene mostrato un processo end-to-end che comprende:
- autenticazione
- creazione della richiesta
- lettura del dettaglio
- presa in carico
- creazione del ticket
- avanzamento del ticket
- chiusura della richiesta
Esempio 3: script Bash per il monitoraggio SLA
Il seguente script Bash consente di interrogare alcuni endpoint statistici di Askme Desk per ottenere informazioni sintetiche sullo stato delle SLA di richieste e ticket.
#!/bin/bash
# Monitoraggio SLA richieste e ticket tramite API Askme Desk
BASE_URL="https://desk.example.com/rest"
KEYCLOAK_URL="https://keycloak.example.com/realms/askme/protocol/openid-connect"
CLIENT_ID="my-integration"
CLIENT_SECRET="secret"
# Ottenimento del token OAuth 2.0
TOKEN=$(curl -s -X POST "$KEYCLOAK_URL/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$CLIENT_ID" \
-d "client_secret=$CLIENT_SECRET" | jq -r '.access_token')
if [ "$TOKEN" = "null" ] || [ -z "$TOKEN" ]; then
echo "Errore: impossibile ottenere il token"
exit 1
fi
echo "=== STATISTICHE SLA RICHIESTE ==="
echo ""
echo "--- SLA presa in carico ---"
curl -s -X GET "$BASE_URL/statistics/slaRichiestePc" \
-H "Authorization: Bearer $TOKEN" | jq '.'
echo ""
echo "--- SLA chiusura ---"
curl -s -X GET "$BASE_URL/statistics/slaRichiesteCh" \
-H "Authorization: Bearer $TOKEN" | jq '.'
echo ""
echo "=== STATISTICHE SLA TICKET ==="
echo ""
echo "--- SLA ticket ---"
curl -s -X GET "$BASE_URL/statistics/slaTicket" \
-H "Authorization: Bearer $TOKEN" | jq '.'
echo ""
echo "--- Tempo residuo SLA ticket ---"
curl -s -X GET "$BASE_URL/statistics/slaTempoResiduoTicket" \
-H "Authorization: Bearer $TOKEN" | jq '.'
echo ""
echo "=== STATISTICHE GENERALI ==="
echo ""
echo "--- Riepilogo richieste fornitore ---"
curl -s -X GET "$BASE_URL/statistics/riepilogoRichiesteFornitore" \
-H "Authorization: Bearer $TOKEN" | jq '.'
Note operative
Questo script può essere utilizzato, ad esempio:
- come comando di controllo manuale
- all’interno di job schedulati
- in pipeline di monitoraggio o integrazione con strumenti esterni
Per l’esecuzione sono richiesti curl e jq.
Esempio 4: creazione massiva di richieste da CSV (Python)
L’esempio seguente mostra come importare richieste di I livello da un file CSV utilizzando il client Python presentato nell’esempio precedente.
import csv
from desk_client import DeskClient # Classe definita nell'Esempio 1
def importa_richieste_da_csv(file_path: str):
"""Importa richieste di I livello da un file CSV."""
client = DeskClient(
base_url="https://desk.example.com/rest",
client_id="my-integration",
client_secret="secret",
keycloak_url="https://keycloak.example.com/realms/askme",
)
risultati = {"ok": 0, "errori": 0}
with open(file_path, newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
try:
info = client.crea_richiesta(
id_servizio=int(row["id_servizio"]),
id_tipo_richiesta=int(row["id_tipo_richiesta"]),
oggetto=row["oggetto"],
descrizione=row.get("descrizione", ""),
idUrgenza=int(row.get("id_urgenza", 1)),
codiceCanale=row.get("canale", "MAIL"),
)
print(f"OK - Richiesta {info['idRichiesta']}: {row['oggetto']}")
risultati["ok"] += 1
except Exception as e:
print(f"ERRORE - {row['oggetto']}: {e}")
risultati["errori"] += 1
print(
f"\nImportazione completata: {risultati['ok']} ok, {risultati['errori']} errori"
)
# File CSV atteso con colonne:
# id_servizio, id_tipo_richiesta, oggetto, descrizione, id_urgenza, canale
importa_richieste_da_csv("richieste.csv")
Formato del file CSV
Il file di input deve contenere almeno le seguenti colonne:
id_servizioid_tipo_richiestaoggetto
Possono inoltre essere presenti campi opzionali, come:
descrizioneid_urgenzacanale
Troubleshooting
Di seguito sono riportati alcuni dei casi più comuni riscontrabili in fase di integrazione.
Errore 401 Unauthorized
Possibili cause:
- token OAuth 2.0 assente, scaduto o non valido
client_idoclient_secreterrati- realm Keycloak non corretto
Verifiche consigliate:
- controllare la configurazione del client OAuth
- verificare l’endpoint di autenticazione
- rinnovare il token prima della scadenza
Errore 403 Forbidden
Possibili cause:
- l’utente o il client non dispongono dei permessi necessari
- i ruoli applicativi non consentono l’operazione richiesta
Verifiche consigliate:
- controllare i ruoli assegnati nel profilo utente Askme Desk
- verificare eventuali restrizioni lato applicativo o lato IAM
Risposta con campo esito* = false
In questo caso la chiamata HTTP è andata a buon fine, ma l’operazione applicativa non è stata eseguita correttamente.
Cause ricorrenti:
- richiesta già chiusa
- ticket senza transizioni disponibili
- conflitto di assegnazione
- dati di input non coerenti con lo stato corrente
Verifiche consigliate:
- controllare il contenuto del campo
message - verificare lo stato attuale dell’entità prima di ripetere l’operazione
Errore in fase di creazione richiesta
Una delle cause più frequenti è la configurazione non valida della terna.
Verificare che la combinazione:
- Servizio
- Asset
- Tipo Richiesta
sia configurata, attiva e utilizzabile.
A questo scopo è possibile interrogare l’endpoint:
GET /terne/lista-terne
Upload allegati non riuscito
Per allegare file a richieste o ticket è necessario seguire il flusso corretto di caricamento:
- ottenere un
uploadIdtramiteGET /upload/upload-id - caricare il file con
POST /upload/, passando file euploadId - utilizzare l’
uploadIdnel campoidUploaddella richiesta o del ticket
Il mancato rispetto di questa sequenza impedisce l’associazione corretta dell’allegato.
Best practice
Per una integrazione più robusta e stabile si consiglia di adottare le seguenti pratiche.
1. Gestione preventiva del token
Rinnovare il token poco prima della scadenza evita errori durante chiamate lunghe o sequenze di operazioni ravvicinate.
2. Verifica preventiva della terna
Prima di creare richieste o ticket, verificare che la combinazione di classificazione sia valida e disponibile.
3. Utilizzo degli endpoint massivi
Per operazioni su più entità è preferibile utilizzare gli endpoint dedicati alle elaborazioni massive, in modo da ridurre il numero di chiamate e migliorare l’efficienza.
4. Controllo esplicito dell’esito applicativo
Uno status HTTP 200 OK non garantisce da solo il buon esito logico dell’operazione. È quindi necessario verificare sempre i campi esito* restituiti dal servizio.
5. Gestione corretta degli allegati
Per l’upload di documenti è necessario seguire la sequenza completa:
uploadId → caricamento file → utilizzo di idUpload nell’operazione finale
6. Utilizzo degli endpoint di esportazione
Gli endpoint CSV risultano particolarmente utili per attività di reporting, analisi esterne e riconciliazione dei dati.