{"id":80985,"date":"2025-05-16T08:44:22","date_gmt":"2025-05-16T07:44:22","guid":{"rendered":"https:\/\/kinsta.com\/it\/?p=80985&#038;preview=true&#038;preview_id=80985"},"modified":"2025-05-19T08:24:18","modified_gmt":"2025-05-19T07:24:18","slug":"aggiungere-interattivita-programmazione-monitoraggio-slackbot","status":"publish","type":"post","link":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/","title":{"rendered":"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress"},"content":{"rendered":"<p>Gli Slackbot non devono rimanere in attesa che vengano digitati i comandi. Con la configurazione giusta, un bot pu\u00f2 permettere di gestire i siti <a href=\"https:\/\/kinsta.com\/it\/blog\/cosa-e-wordpress\/\">WordPress<\/a> con pulsanti interattivi, menu a tendina, attivit\u00e0 programmate e avvisi intelligenti, il tutto all&#8217;interno di <a href=\"https:\/\/kinsta.com\/it\/blog\/come-utilizzare-slack\/\">Slack<\/a>.<\/p>\n<p>In questo articolo mostreremo come aggiungere funzioni di interattivit\u00e0, automazione e monitoraggio ad un bot Slack.<\/p>\n<div><\/div><kinsta-auto-toc heading=\"Table of Contents\" exclude=\"last\" list-style=\"arrow\" selector=\"h2\" count-number=\"-1\"><\/kinsta-auto-toc>\n<h2>Prerequisiti<\/h2>\n<p>Prima di iniziare, \u00e8 necessario assicurasi di avere:<\/p>\n<ul>\n<li>Un&#8217;app Slack con permessi bot e un comando slash.<\/li>\n<li>Un <a href=\"https:\/\/kinsta.com\/it\/\">account Kinsta<\/a> con accesso alle API e un sito da testare.<\/li>\n<li>Node.js e NPM installati in locale.<\/li>\n<li>Una familiarit\u00e0 di base con JavaScript (o almeno una certa dimestichezza nel copiare e modificare il codice).<\/li>\n<li>Chiavi API per Slack e Kinsta.<\/li>\n<\/ul>\n<h2>Come iniziare<\/h2>\n<p>Per costruire questo Slackbot, utilizziamo <a href=\"https:\/\/kinsta.com\/it\/blog\/node-js\/\">Node.js<\/a> e il <a href=\"https:\/\/api.slack.com\/bolt\" target=\"_blank\" rel=\"noopener noreferrer\">framework Bolt<\/a> di Slack per collegare i comandi slash che attivano le azioni tramite l&#8217;API di Kinsta.<\/p>\n<p>In questa guida non ripeteremo tutti i passaggi per la creazione di un&#8217;app Slack o per ottenere l&#8217;accesso all&#8217;API di Kinsta, in quanto questi aspetti sono gi\u00e0 stati trattati nella nostra guida precedente, <a href=\"https:\/\/kinsta.com\/it\/blog\/creare-slackbot-gestire-sito\/\">Come costruire uno Slackbot con Node.js e l&#8217;API di Kinsta per la gestione di un sito<\/a>.<\/p>\n<p>Consigliamo a chi non l&#8217;avesse ancora letta, di leggerla prima. La guida spiega come creare un&#8217;app Slack, come ottenere il token del bot e il segreto di firma e come ottenere la chiave API di Kinsta.<\/p>\n<h2>Aggiungere interattivit\u00e0 ad uno Slackbot<\/h2>\n<p>Gli Slackbot non devono affidarsi solo ai comandi slash. Con componenti interattivi come pulsanti, menu e finestre modali, possiamo trasformare un bot in uno strumento molto pi\u00f9 intuitivo.<\/p>\n<p>Invece di digitare <code>\/clear_cache environment_id<\/code>, immaginiamo di cliccare su un pulsante con la dicitura <strong>Clear Cache<\/strong> subito dopo aver controllato lo stato di un sito. Per farlo, abbiamo bisogno del <a href=\"https:\/\/www.npmjs.com\/package\/@slack\/web-api\" target=\"_blank\" rel=\"noopener noreferrer\">client Web API di Slack<\/a>. Possiamo installarlo nel progetto con il seguente comando:<\/p>\n<pre><code class=\"language-bash\">npm install @slack\/web-api<\/code><\/pre>\n<p>Poi inizializziamolo nel nostro sito <code>app.js<\/code>:<\/p>\n<pre><code class=\"language-js\">const { WebClient } = require('@slack\/web-api');\nconst web = new WebClient(process.env.SLACK_BOT_TOKEN);<\/code><\/pre>\n<p>Assicuriamoci che nel file <code>.env<\/code> sia impostato <code>SLACK_BOT_TOKEN<\/code>. Ora miglioriamo il comando <code>\/site_status<\/code> dell&#8217;articolo precedente. Invece di inviare solo del testo, aggiungiamo dei pulsanti per azioni rapide come <strong>Cancella cache<\/strong>, <strong>Crea backup<\/strong> o <strong>Controlla stato dettagliato<\/strong>.<\/p>\n<p>Ecco come appare il gestore aggiornato:<\/p>\n<pre><code class=\"language-js\">app.command('\/site_status', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  const environmentId = command.text.trim();\n  \n  if (!environmentId) {\n    await say('Please provide an environment ID. Usage: `\/site_status [environment-id]`');\n    return;\n  }\n  \n  try {\n    \/\/ Get environment status\n    const response = await kinstaRequest(`\/sites\/environments\/${environmentId}`);\n    \n    if (response && response.site && response.site.environments && response.site.environments.length &gt; 0) {\n      const env = response.site.environments[0];\n      \n      \/\/ Format the status message\n      let statusMessage = formatSiteStatus(env);\n      \n      \/\/ Send message with interactive buttons\n      await web.chat.postMessage({\n        channel: command.channel_id,\n        text: statusMessage,\n        blocks: [\n          {\n            type: 'section',\n            text: {\n              type: 'mrkdwn',\n              text: statusMessage\n            }\n          },\n          {\n            type: 'actions',\n            elements: [\n              {\n                type: 'button',\n                text: {\n                  type: 'plain_text',\n                  text: '\ud83e\uddf9 Clear Cache',\n                  emoji: true\n                },\n                value: environmentId,\n                action_id: 'clear_cache_button'\n              },\n              {\n                type: 'button',\n                text: {\n                  type: 'plain_text',\n                  text: '\ud83d\udcca Detailed Status',\n                  emoji: true\n                },\n                value: environmentId,\n                action_id: 'detailed_status_button'\n              },\n              {\n                type: 'button',\n                text: {\n                  type: 'plain_text',\n                  text: '\ud83d\udcbe Create Backup',\n                  emoji: true\n                },\n                value: environmentId,\n                action_id: 'create_backup_button'\n              }\n            ]\n          }\n        ]\n      });\n    } else {\n      await say(`\u26a0\ufe0f No environment found with ID: `${environmentId}``);\n    }\n  } catch (error) {\n    console.error('Error checking site status:', error);\n    await say(`\u274c Error checking site status: ${error.message}`);\n  }\n});<\/code><\/pre>\n<p>Ogni clic sul pulsante attiva un&#8217;azione. Ecco come gestiamo il pulsante <strong>Cancella cache<\/strong>:<\/p>\n<pre><code class=\"language-js\">\/\/ Add action handlers for the buttons\napp.action('clear_cache_button', async ({ body, ack, respond }) =&gt; {\n  await ack();\n  \n  const environmentId = body.actions[0].value;\n  \n  await respond(`\ud83d\udd04 Clearing cache for environment `${environmentId}`...`);\n  \n  try {\n    \/\/ Call Kinsta API to clear cache\n    const response = await kinstaRequest(\n      `\/sites\/environments\/${environmentId}\/clear-cache`,\n      'POST'\n    );\n    \n    if (response && response.operation_id) {\n      await respond(`\u2705 Cache clearing operation started! Operation ID: `${response.operation_id}``);\n    } else {\n      await respond('\u26a0\ufe0f Cache clearing request was sent, but no operation ID was returned.');\n    }\n  } catch (error) {\n    console.error('Cache clearing error:', error);\n    await respond(`\u274c Error clearing cache: ${error.message}`);\n  }\n});<\/code><\/pre>\n<p>Possiamo seguire lo stesso schema per i pulsanti di backup e di stato, collegando ciascuno di essi all&#8217;endpoint dell&#8217;API o alla logica di comando appropriata.<\/p>\n<pre><code class=\"language-js\">\/\/ Handlers for other buttons\napp.action('detailed_status_button', async ({ body, ack, respond }) =&gt; {\n  await ack();\n  const environmentId = body.actions[0].value;\n  \/\/ Implement detailed status check similar to the \/detailed_status command\n  \/\/ ...\n});\n\napp.action('create_backup_button', async ({ body, ack, respond }) =&gt; {\n  await ack();\n  const environmentId = body.actions[0].value;\n  \/\/ Implement backup creation similar to the \/create_backup command\n  \/\/ ...\n});<\/code><\/pre>\n<h3>Un menu a tendina per selezionare un sito<\/h3>\n<p>Digitare gli ID degli ambienti non \u00e8 divertente. E aspettarsi che ogni membro del team ricordi quale ID appartiene a quale ambiente? Non \u00e8 realistico.<\/p>\n<p>Rendiamo il tutto pi\u00f9 intuitivo. Invece di chiedere agli utenti di digitare <code>\/site_status [environment-id]<\/code>, daremo loro un menu a tendina di Slack in cui potranno scegliere un sito da un elenco. Una volta selezionato, il bot ne mostrer\u00e0 lo stato e allegher\u00e0 gli stessi pulsanti di azione rapida che abbiamo implementato in precedenza.<\/p>\n<p>Per farlo:<\/p>\n<ul>\n<li>recuperiamo tutti i siti dall&#8217;API di Kinsta<\/li>\n<li>recuperiamo gli ambienti per ogni sito<\/li>\n<li>creiamo un menu a tendina con queste opzioni<\/li>\n<li>gestiamo la selezione dell&#8217;utente e visualizziamo lo stato del sito<\/li>\n<\/ul>\n<p>Ecco il comando che mostra il menu a tendina:<\/p>\n<pre><code class=\"language-js\">app.command('\/select_site', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  try {\n    \/\/ Get all sites\n    const response = await kinstaRequest('\/sites');\n    \n    if (response && response.company && response.company.sites) {\n      const sites = response.company.sites;\n      \n      \/\/ Create options for each site\n      const options = [];\n      \n      for (const site of sites) {\n        \/\/ Get environments for this site\n        const envResponse = await kinstaRequest(`\/sites\/${site.id}\/environments`);\n        \n        if (envResponse && envResponse.site && envResponse.site.environments) {\n          for (const env of envResponse.site.environments) {\n            options.push({\n              text: {\n                type: 'plain_text',\n                text: `${site.name} (${env.name})`\n              },\n              value: env.id\n            });\n          }\n        }\n      }\n      \n      \/\/ Send message with dropdown\n      await web.chat.postMessage({\n        channel: command.channel_id,\n        text: 'Select a site to manage:',\n        blocks: [\n          {\n            type: 'section',\n            text: {\n              type: 'mrkdwn',\n              text: '*Select a site to manage:*'\n            },\n            accessory: {\n              type: 'static_select',\n              placeholder: {\n                type: 'plain_text',\n                text: 'Select a site'\n              },\n              options: options.slice(0, 100), \/\/ Slack has a limit of 100 options\n              action_id: 'site_selected'\n            }\n          }\n        ]\n      });\n    } else {\n      await say('\u274c Error retrieving sites. Please check your API credentials.');\n    }\n  } catch (error) {\n    console.error('Error:', error);\n    await say(`\u274c Error retrieving sites: ${error.message}`);\n  }\n});<\/code><\/pre>\n<p>Quando l&#8217;utente sceglie un sito, lo gestiamo con questo gestore di azioni:<\/p>\n<pre><code class=\"language-js\">\/\/ Handle the site selection\napp.action('site_selected', async ({ body, ack, respond }) =&gt; {\n  await ack();\n  \n  const environmentId = body.actions[0].selected_option.value;\n  const siteName = body.actions[0].selected_option.text.text;\n  \n  \/\/ Get environment status\n  try {\n    const response = await kinstaRequest(`\/sites\/environments\/${environmentId}`);\n    \n    if (response && response.site && response.site.environments && response.site.environments.length &gt; 0) {\n      const env = response.site.environments[0];\n      \n      \/\/ Format the status message\n      let statusMessage = `*${siteName}* (ID: `${environmentId}`)nn${formatSiteStatus(env)}`;\n      \n      \/\/ Send message with interactive buttons (similar to the site_status command)\n      \/\/ ...\n    } else {\n      await respond(`\u26a0\ufe0f No environment found with ID: `${environmentId}``);\n    }\n  } catch (error) {\n    console.error('Error:', error);\n    await respond(`\u274c Error retrieving environment: ${error.message}`);\n  }\n});<\/code><\/pre>\n<p>Ora che il nostro bot pu\u00f2 attivare azioni con un pulsante e selezionare siti da un elenco, assicuriamoci di fare in modo che non vengano eseguite accidentalmente operazioni rischiose.<\/p>\n<h3>Finestre di conferma<\/h3>\n<p>Alcune operazioni non dovrebbero mai essere eseguite accidentalmente. Cancellare la cache pu\u00f2 sembrare un&#8217;operazione innocua, ma se si lavora su un sito di produzione, probabilmente non bisogner\u00e0 farlo con un solo clic, soprattutto se si sta solo controllando lo stato del sito. \u00c8 qui che entrano in gioco i modali (finestre di dialogo) di Slack.<\/p>\n<p>Invece di cancellare immediatamente la cache cliccando su <code>clear_cache_button<\/code>, \u00e8 preferibile mostrare una finestra di conferma. Ecco come fare:<\/p>\n<pre><code class=\"language-js\">app.action('clear_cache_button', async ({ body, ack, context }) =&gt; {\n  await ack();\n  \n  const environmentId = body.actions[0].value;\n  \n  \/\/ Open a confirmation dialog\n  try {\n    await web.views.open({\n      trigger_id: body.trigger_id,\n      view: {\n        type: 'modal',\n        callback_id: 'clear_cache_confirmation',\n        private_metadata: environmentId,\n        title: {\n          type: 'plain_text',\n          text: 'Confirm Cache Clearing'\n        },\n        blocks: [\n          {\n            type: 'section',\n            text: {\n              type: 'mrkdwn',\n              text: `Are you sure you want to clear the cache for environment `${environmentId}`?`\n            }\n          }\n        ],\n        submit: {\n          type: 'plain_text',\n          text: 'Clear Cache'\n        },\n        close: {\n          type: 'plain_text',\n          text: 'Cancel'\n        }\n      }\n    });\n  } catch (error) {\n    console.error('Error opening confirmation dialog:', error);\n  }\n});<\/code><\/pre>\n<p>Nel codice qui sopra, utilizziamo <code>web.views.open()<\/code> per lanciare una finestra modale con un titolo chiaro, un messaggio di avviso e due pulsanti &#8211; <strong>Clear Cache<\/strong> e <strong>Cancel<\/strong> &#8211; e memorizziamo <code>environmentId<\/code> in <code>private_metadata<\/code> in modo da averlo a disposizione quando l&#8217;utente clicca su <strong>Clear Cache<\/strong>.<\/p>\n<p>Una volta che l&#8217;utente abbia cliccato sul pulsante <strong>Clear Cache<\/strong> del modale, Slack invia un evento <code>view_submission<\/code>. Ecco come gestirlo e come procedere con l&#8217;operazione vera e propria:<\/p>\n<pre><code class=\"language-js\">\/\/ Handle the confirmation dialog submission\napp.view('clear_cache_confirmation', async ({ ack, body, view }) =&gt; {\n  await ack();\n  \n  const environmentId = view.private_metadata;\n  const userId = body.user.id;\n  \n  \/\/ Find a DM channel with the user to respond to\n  const result = await web.conversations.open({\n    users: userId\n  });\n  \n  const channel = result.channel.id;\n  \n  await web.chat.postMessage({\n    channel,\n    text: `\ud83d\udd04 Clearing cache for environment `${environmentId}`...`\n  });\n  \n  try {\n    \/\/ Call Kinsta API to clear cache\n    const response = await kinstaRequest(\n      `\/sites\/environments\/${environmentId}\/clear-cache`,\n      'POST'\n    );\n    \n    if (response && response.operation_id) {\n      await web.chat.postMessage({\n        channel,\n        text: `\u2705 Cache clearing operation started! Operation ID: `${response.operation_id}``\n      });\n    } else {\n      await web.chat.postMessage({\n        channel,\n        text: '\u26a0\ufe0f Cache clearing request was sent, but no operation ID was returned.'\n      });\n    }\n  } catch (error) {\n    console.error('Cache clearing error:', error);\n    await web.chat.postMessage({\n      channel,\n      text: `\u274c Error clearing cache: ${error.message}`\n    });\n  }\n});<\/code><\/pre>\n<p>In questo codice, dopo la conferma dell&#8217;utente, prendiamo l&#8217;evento <code>environmentId<\/code> da <code>private_metadata<\/code>, apriamo un DM privato usando <code>web.conversations.open()<\/code> per evitare di ingombrare i canali pubblici, eseguiamo la richiesta API per cancellare la cache e proseguiamo con un messaggio di successo o di errore a seconda del risultato.<\/p>\n<h3>Indicatori di avanzamento<\/h3>\n<p>Alcuni comandi di Slack sono immediati, come la cancellazione della cache o la verifica dello stato. Ma non tutti.<\/p>\n<p>Creare un backup o distribuire dei file pu\u00f2 richiedere diversi secondi o addirittura minuti. Se durante questo tempo il bot rimane in silenzio, gli utenti potrebbero pensare che si sia rotto qualcosa.<\/p>\n<p>Slack non offre una barra di avanzamento nativa, ma possiamo simulare una barra di avanzamento con un po&#8217; di creativit\u00e0. Ecco una funzione di aiuto che aggiorna un messaggio con una barra di avanzamento visiva utilizzando il kit dei blocchi:<\/p>\n<pre><code class=\"language-js\">async function updateProgress(channel, messageTs, text, percentage) {\n  \/\/ Create a progress bar\n  const barLength = 20;\n  const filledLength = Math.round(barLength * (percentage \/ 100));\n  const bar = '\u2588'.repeat(filledLength) + '\u2591'.repeat(barLength - filledLength);\n  \n  await web.chat.update({\n    channel,\n    ts: messageTs,\n    text: `${text} [${percentage}%]`,\n    blocks: [\n      {\n        type: 'section',\n        text: {\n          type: 'mrkdwn',\n          text: `${text} [${percentage}%]n`${bar}``\n        }\n      }\n    ]\n  });\n}<\/code><\/pre>\n<p>Integriamo questa funzione in un comando di <code>\/create_backup<\/code>. Prima di rispondere, invece di aspettare il completamento dell&#8217;intera operazione, controlleremo l&#8217;utente a ogni passaggio.<\/p>\n<pre><code class=\"language-js\">app.command('\/create_backup', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  const args = command.text.split(' ');\n  const environmentId = args[0];\n  const tag = args.length &gt; 1 ? args.slice(1).join(' ') : `Manual backup ${new Date().toISOString()}`;\n  \n  if (!environmentId) {\n    await say('Please provide an environment ID. Usage: `\/create_backup [environment-id] [optional-tag]`');\n    return;\n  }\n  \n  \/\/ Post initial message and get its timestamp for updates\n  const initial = await say('\ud83d\udd04 Initiating backup...');\n  const messageTs = initial.ts;\n  \n  try {\n    \/\/ Update progress to 10%\n    await updateProgress(command.channel_id, messageTs, '\ud83d\udd04 Creating backup...', 10);\n    \n    \/\/ Call Kinsta API to create a backup\n    const response = await kinstaRequest(\n      `\/sites\/environments\/${environmentId}\/manual-backups`,\n      'POST',\n      { tag }\n    );\n    \n    if (response && response.operation_id) {\n      await updateProgress(command.channel_id, messageTs, '\ud83d\udd04 Backup in progress...', 30);\n      \n      \/\/ Poll the operation status\n      let completed = false;\n      let percentage = 30;\n      \n      while (!completed && percentage  setTimeout(resolve, 3000));\n        \n        \/\/ Check operation status\n        const statusResponse = await kinstaRequest(`\/operations\/${response.operation_id}`);\n        \n        if (statusResponse && statusResponse.operation) {\n          const operation = statusResponse.operation;\n          \n          if (operation.status === 'completed') {\n            completed = true;\n            percentage = 100;\n          } else if (operation.status === 'failed') {\n            await web.chat.update({\n              channel: command.channel_id,\n              ts: messageTs,\n              text: `\u274c Backup failed! Error: ${operation.error || 'Unknown error'}`\n            });\n            return;\n          } else {\n            \/\/ Increment progress\n            percentage += 10;\n            if (percentage &gt; 95) percentage = 95;\n            \n            await updateProgress(\n              command.channel_id, \n              messageTs, \n              '\ud83d\udd04 Backup in progress...', \n              percentage\n            );\n          }\n        }\n      }\n      \n      \/\/ Final update\n      await web.chat.update({\n        channel: command.channel_id,\n        ts: messageTs,\n        text: `\u2705 Backup completed successfully!`,\n        blocks: [\n          {\n            type: 'section',\n            text: {\n              type: 'mrkdwn',\n              text: `\u2705 Backup completed successfully!n*Tag:* ${tag}n*Operation ID:* `${response.operation_id}``\n            }\n          }\n        ]\n      });\n    } else {\n      await web.chat.update({\n        channel: command.channel_id,\n        ts: messageTs,\n        text: '\u26a0\ufe0f Backup request was sent, but no operation ID was returned.'\n      });\n    }\n  } catch (error) {\n    console.error('Backup creation error:', error);\n    \n    await web.chat.update({\n      channel: command.channel_id,\n      ts: messageTs,\n      text: `\u274c Error creating backup: ${error.message}`\n    });\n  }\n});<\/code><\/pre>\n<h3>Notifiche di conferma\/errore<\/h3>\n<p>Al momento, il nostro bot probabilmente invia un testo semplice come <strong>\u2705 Success<\/strong> o <strong>\u274c Failed<\/strong>. Funziona, ma \u00e8 scarno e non aiuta gli utenti a capire <em>perch\u00e9<\/em> qualcosa \u00e8 andato a buon fine o cosa devono fare in caso di errore.<\/p>\n<p>Risolviamo il problema con una formattazione corretta per i messaggi di conferma e di errore, con un contesto utile, suggerimenti e una formattazione pulita.<\/p>\n<p>Aggiungiamo queste utility al nostro sito <code>utils.js<\/code> in modo da poterle riutilizzare per tutti i comandi:<\/p>\n<pre><code class=\"language-js\">function formatSuccessMessage(title, details = []) {\n  let message = `\u2705 *${title}*nn`;\n  \n  if (details.length &gt; 0) {\n    details.forEach(detail =&gt; {\n      message += `\u2022 ${detail.label}: ${detail.value}n`;\n    });\n  }\n  \n  return message;\n}\n\nfunction formatErrorMessage(title, error, suggestions = []) {\n  let message = `\u274c *${title}*nn`;\n  message += `*Error:* ${error}nn`;\n  \n  if (suggestions.length &gt; 0) {\n    message += '*Suggestions:*n';\n    suggestions.forEach(suggestion =&gt; {\n      message += `\u2022 ${suggestion}n`;\n    });\n  }\n  \n  return message;\n}\n\nmodule.exports = {\n  connectToSite,\n  logCommand,\n  formatSuccessMessage,\n  formatErrorMessage\n};<\/code><\/pre>\n<p>Queste funzioni prendono input strutturati e li trasformano in markdown adatti a Slack con emoji, etichette e interruzioni di riga. Molto pi\u00f9 facile da comprendere nel bel mezzo di un&#8217;intensa discussione su Slack. Ecco come appare all&#8217;interno di un vero gestore di comandi. Utilizziamo come esempio <code>\/clear_cache<\/code>:<\/p>\n<pre><code class=\"language-js\">app.command('\/clear_cache', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  const environmentId = command.text.trim();\n  \n  if (!environmentId) {\n    await say('Please provide an environment ID. Usage: `\/clear_cache [environment-id]`');\n    return;\n  }\n  \n  try {\n    await say('\ud83d\udd04 Processing...');\n    \n    \/\/ Call Kinsta API to clear cache\n    const response = await kinstaRequest(\n      `\/sites\/environments\/${environmentId}\/clear-cache`,\n      'POST'\n    );\n    \n    if (response && response.operation_id) {\n      const { formatSuccessMessage } = require('.\/utils');\n      \n      await say(formatSuccessMessage('Cache Clearing Started', [\n        { label: 'Environment ID', value: ``${environmentId}`` },\n        { label: 'Operation ID', value: ``${response.operation_id}`` },\n        { label: 'Status', value: 'In Progress' }\n      ]));\n    } else {\n      const { formatErrorMessage } = require('.\/utils');\n      \n      await say(formatErrorMessage(\n        'Cache Clearing Error',\n        'No operation ID returned',\n        [\n          'Check your environment ID',\n          'Verify your API credentials',\n          'Try again later'\n        ]\n      ));\n    }\n  } catch (error) {\n    console.error('Cache clearing error:', error);\n    \n    const { formatErrorMessage } = require('.\/utils');\n    \n    await say(formatErrorMessage(\n      'Cache Clearing Error',\n      error.message,\n      [\n        'Check your environment ID',\n        'Verify your API credentials',\n        'Try again later'\n      ]\n    ));\n  }\n});<\/code><\/pre>\n<h2>Automatizzare le attivit\u00e0 di WordPress con operazioni programmate<\/h2>\n<p>Finora, tutto ci\u00f2 che il nostro Slackbot fa avviene quando qualcuno attiva esplicitamente un comando. Ma non tutto deve dipendere dal fatto che qualcuno si ricordi di eseguire un comando.<\/p>\n<p>E se il nostro bot potesse eseguire automaticamente il backup dei siti ogni sera? E se potesse controllare se un sito \u00e8 gi\u00f9 ogni mattina prima che il team si svegli.<\/p>\n<p>Utilizzeremo la libreria <a href=\"https:\/\/www.npmjs.com\/package\/node-schedule\" target=\"_blank\" rel=\"noopener noreferrer\">node-schedule<\/a> per eseguire attivit\u00e0 basate su espressioni cron. Per prima cosa, installiamola con:<\/p>\n<pre><code class=\"language-bash\">npm install node-schedule<\/code><\/pre>\n<p>Ora impostiamola all&#8217;inizio dell&#8217;<code>app.js<\/code> del nostro sito:<\/p>\n<pre><code class=\"language-js\">const schedule = require('node-schedule');<\/code><\/pre>\n<p>Avremo anche bisogno di un modo per tenere traccia delle operazioni programmate attive, in modo che gli utenti possano listarle o cancellarle:<\/p>\n<pre><code class=\"language-js\">const scheduledJobs = {};<\/code><\/pre>\n<h3>Creare il comando di programmazione delle attivit\u00e0<\/h3>\n<p>Inizieremo con un comando di base <code>\/schedule_task<\/code> che accetta un tipo di attivit\u00e0 (<code>backup<\/code>, <code>clear_cache<\/code> o <code>status_check<\/code>), l&#8217;ID dell&#8217;ambiente e un&#8217;espressione cron.<\/p>\n<pre><code class=\"language-bash\">\/schedule_task backup 12345 0 0 * * *<\/code><\/pre>\n<p>Questo comando programma un backup giornaliero a mezzanotte. Ecco il gestore completo del comando:<\/p>\n<pre><code class=\"language-js\">app.command('\/schedule_task', async ({ command, ack, say }) =&gt; {\n  await ack();\n\n  const args = command.text.split(' ');\n  if (args.length  {\n      console.log(`Running scheduled ${taskType} for environment ${environmentId}`);\n\n      try {\n        switch (taskType) {\n          case 'backup':\n            await kinstaRequest(`\/sites\/environments\/${environmentId}\/manual-backups`, 'POST', {\n              tag: `Scheduled backup ${new Date().toISOString()}`\n            });\n            break;\n          case 'clear_cache':\n            await kinstaRequest(`\/sites\/environments\/${environmentId}\/clear-cache`, 'POST');\n            break;\n          case 'status_check':\n            const response = await kinstaRequest(`\/sites\/environments\/${environmentId}`);\n            const env = response?.site?.environments?.[0];\n            if (env) {\n              console.log(`Status: ${env.display_name} is ${env.is_blocked ? 'blocked' : 'running'}`);\n            }\n            break;\n        }\n      } catch (err) {\n        console.error(`Scheduled ${taskType} failed for ${environmentId}:`, err.message);\n      }\n    });\n\n    scheduledJobs[jobId] = {\n      job,\n      taskType,\n      environmentId,\n      cronSchedule,\n      userId: command.user_id,\n      createdAt: new Date().toISOString()\n    };\n\n    await say(`\u2705 Scheduled task created!\n*Task:* ${taskType}\n*Environment:* `${environmentId}`\n*Cron:* `${cronSchedule}`\n*Job ID:* `${jobId}`\n\nTo cancel this task, run `\/cancel_task ${jobId}``);\n  } catch (err) {\n    console.error('Error creating scheduled job:', err);\n    await say(`\u274c Failed to create scheduled task: ${err.message}`);\n  }\n});<\/code><\/pre>\n<h3>Annullare le attivit\u00e0 pianificate<\/h3>\n<p>Se qualcosa cambia o se l&#8217;attivit\u00e0 non \u00e8 pi\u00f9 necessaria, gli utenti possono annullarla con:<\/p>\n<pre><code class=\"language-bash\">\/cancel_task<\/code><\/pre>\n<p>Ecco l&#8217;implementazione:<\/p>\n<pre><code class=\"language-js\">app.command('\/cancel_task', async ({ command, ack, say }) =&gt; {\n  await ack();\n\n  const jobId = command.text.trim();\n\n  if (!scheduledJobs[jobId]) {\n    await say(`\u26a0\ufe0f No task found with ID: `${jobId}``);\n    return;\n  }\n\n  scheduledJobs[jobId].job.cancel();\n  delete scheduledJobs[jobId];\n\n  await say(`\u2705 Task `${jobId}` has been cancelled.`);\n});<\/code><\/pre>\n<h3>Elenco di tutte le attivit\u00e0 programmate<\/h3>\n<p>Permettiamo agli utenti di visualizzare tutti i lavori che sono stati programmati:<\/p>\n<pre><code class=\"language-js\">app.command('\/list_tasks', async ({ command, ack, say }) =&gt; {\n  await ack();\n\n  const tasks = Object.entries(scheduledJobs);\n  if (tasks.length === 0) {\n    await say('No scheduled tasks found.');\n    return;\n  }\n\n  let message = '*Scheduled Tasks:*nn';\n\n  for (const [jobId, job] of tasks) {\n    message += `\u2022 *Job ID:* `${jobId}`n`;\n    message += `  - Task: ${job.taskType}n`;\n    message += `  - Environment: `${job.environmentId}`n`;\n    message += `  - Cron: `${job.cronSchedule}`n`;\n    message += `  - Created by: nn`;\n  }\n\n  message += '_Use `\/cancel_task [job_id]` to cancel a task._';\n  await say(message);\n});<\/code><\/pre>\n<p>Questo d\u00e0 al nostro Slackbot un nuovo livello di autonomia. I backup, la pulizia della cache e i controlli di stato non devono pi\u00f9 essere compito di qualcuno. Avranno luogo in modo silenzioso, affidabile e puntuale.<\/p>\n<h2>Manutenzione periodica<\/h2>\n<p>A volte si desidera eseguire un gruppo di attivit\u00e0 di manutenzione a intervalli regolari, come i backup settimanali e la pulizia della cache la domenica sera. \u00c8 qui che entrano in gioco le finestre di manutenzione.<\/p>\n<p>Una finestra di manutenzione \u00e8 un intervallo di tempo programmato in cui il bot esegue automaticamente attivit\u00e0 predefinite come:<\/p>\n<ul>\n<li>Creare un backup<\/li>\n<li>Cancellare la cache<\/li>\n<li>Inviare notifiche di avvio e completamento<\/li>\n<\/ul>\n<p>Il formato \u00e8 semplice:<\/p>\n<pre><code class=\"language-bash\">\/maintenance_window [environment_id] [day_of_week] [hour] [duration_hours]<\/code><\/pre>\n<p>Ad esempio:<\/p>\n<pre><code class=\"language-bash\">\/maintenance_window 12345 Sunday 2 3<\/code><\/pre>\n<p>Questo significa che ogni domenica alle 2 del mattino, le attivit\u00e0 di manutenzione vengono eseguite per 3 ore. Ecco l&#8217;implementazione completa:<\/p>\n<pre><code class=\"language-js\">\/\/ Add a command to create a maintenance window\napp.command('\/maintenance_window', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  \/\/ Expected format: environment_id day_of_week hour duration\n  \/\/ Example: \/maintenance_window 12345 Sunday 2 3\n  const args = command.text.split(' ');\n  \n  if (args.length &lt; 4) {\n    await say('Please provide all required parameters. Usage: `\/maintenance_window [environment_id] [day_of_week] [hour] [duration_hours]`');\n    return;\n  }\n  \n  const [environmentId, dayOfWeek, hour, duration] = args;\n  \n  \/\/ Validate inputs\n  const validDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n  if (!validDays.includes(dayOfWeek)) {\n    await say(`Invalid day of week. Please choose from: ${validDays.join(', ')}`);\n    return;\n  }\n  \n  const hourInt = parseInt(hour, 10);\n  if (isNaN(hourInt) || hourInt  23) {\n    await say('Hour must be a number between 0 and 23.');\n    return;\n  }\n  \n  const durationInt = parseInt(duration, 10);\n  if (isNaN(durationInt) || durationInt  12) {\n    await say('Duration must be a number between 1 and 12 hours.');\n    return;\n  }\n  \n  \/\/ Convert day of week to cron format\n  const dayMap = {\n    'Sunday': 0,\n    'Monday': 1,\n    'Tuesday': 2,\n    'Wednesday': 3,\n    'Thursday': 4,\n    'Friday': 5,\n    'Saturday': 6\n  };\n  \n  const cronDay = dayMap[dayOfWeek];\n  \n  \/\/ Create cron schedule for the start of the maintenance window\n  const cronSchedule = `0 ${hourInt} * * ${cronDay}`;\n  \n  \/\/ Generate a unique job ID\n  const jobId = `maintenance_${environmentId}_${Date.now()}`;\n  \n  \/\/ Schedule the job\n  try {\n    const job = schedule.scheduleJob(cronSchedule, async function() {\n      \/\/ Start of maintenance window\n      await web.chat.postMessage({\n        channel: command.channel_id,\n        text: `\ud83d\udd27 *Maintenance Window Started*n*Environment:* `${environmentId}`n*Duration:* ${durationInt} hoursnnAutomatic maintenance tasks are now running.`\n      });\n      \n      \/\/ Perform maintenance tasks\n      try {\n        \/\/ 1. Create a backup\n        const backupResponse = await kinstaRequest(\n          `\/sites\/environments\/${environmentId}\/manual-backups`,\n          'POST',\n          { tag: `Maintenance backup ${new Date().toISOString()}` }\n        );\n        \n        if (backupResponse && backupResponse.operation_id) {\n          await web.chat.postMessage({\n            channel: command.channel_id,\n            text: `\u2705 Maintenance backup created. Operation ID: `${backupResponse.operation_id}``\n          });\n        }\n        \n        \/\/ 2. Clear cache\n        const cacheResponse = await kinstaRequest(\n          `\/sites\/environments\/${environmentId}\/clear-cache`,\n          'POST'\n        );\n        \n        if (cacheResponse && cacheResponse.operation_id) {\n          await web.chat.postMessage({\n            channel: command.channel_id,\n            text: `\u2705 Cache cleared. Operation ID: `${cacheResponse.operation_id}``\n          });\n        }\n        \n        \/\/ 3. Schedule end of maintenance window notification\n        setTimeout(async () =&gt; {\n          await web.chat.postMessage({\n            channel: command.channel_id,\n            text: `\u2705 *Maintenance Window Completed*n*Environment:* `${environmentId}`nnAll maintenance tasks have been completed.`\n          });\n        }, durationInt * 60 * 60 * 1000); \/\/ Convert hours to milliseconds\n      } catch (error) {\n        console.error('Maintenance tasks error:', error);\n        await web.chat.postMessage({\n          channel: command.channel_id,\n          text: `\u274c Error during maintenance: ${error.message}`\n        });\n      }\n    });\n    \n    \/\/ Store the job for later cancellation\n    scheduledJobs[jobId] = {\n      job,\n      taskType: 'maintenance',\n      environmentId,\n      cronSchedule,\n      dayOfWeek,\n      hour: hourInt,\n      duration: durationInt,\n      userId: command.user_id,\n      createdAt: new Date().toISOString()\n    };\n    \n    await say(`\u2705 Maintenance window scheduled!\n*Environment:* `${environmentId}`\n*Schedule:* Every ${dayOfWeek} at ${hourInt}:00 for ${durationInt} hours\n*Job ID:* `${jobId}`\n\nTo cancel this maintenance window, use `\/cancel_task ${jobId}``);\n  } catch (error) {\n    console.error('Error scheduling maintenance window:', error);\n    await say(`\u274c Error scheduling maintenance window: ${error.message}`);\n  }\n});<\/code><\/pre>\n<h3>Reportistica automatizzata<\/h3>\n<p>Non \u00e8 bello svegliarsi ogni luned\u00ec chiedendosi se il sito \u00e8 stato sottoposto a backup o se \u00e8 stato gi\u00f9 per ore. Con i report automatici, il nostro bot Slack pu\u00f2 fornire a noi e al nostro team un riepilogo delle prestazioni in base a un calendario.<\/p>\n<p>Questo tipo di report \u00e8 ottimo per tenere sotto controllo aspetti quali:<\/p>\n<ul>\n<li>Stato attuale del sito<\/li>\n<li>Operazioni di backup degli ultimi 7 giorni<\/li>\n<li>Versione di PHP e dominio primario<\/li>\n<li>Eventuali bandiere rosse, come ambienti bloccati o backup mancanti<\/li>\n<\/ul>\n<p>Creiamo un comando <code>\/schedule_report<\/code> che automatizzi tutte queste operazioni.<\/p>\n<pre><code class=\"language-js\">\/\/ Add a command to schedule weekly reporting\napp.command('\/schedule_report', async ({ command, ack, say }) =&gt; {\n  await ack();\n  \n  \/\/ Expected format: environment_id day_of_week hour\n  \/\/ Example: \/schedule_report 12345 Monday 9\n  const args = command.text.split(' ');\n  \n  if (args.length &lt; 3) {\n    await say('Please provide all required parameters. Usage: `\/schedule_report [environment_id] [day_of_week] [hour]`');\n    return;\n  }\n  \n  const [environmentId, dayOfWeek, hour] = args;\n  \n  \/\/ Validate inputs\n  const validDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];\n  if (!validDays.includes(dayOfWeek)) {\n    await say(`Invalid day of week. Please choose from: ${validDays.join(', ')}`);\n    return;\n  }\n  \n  const hourInt = parseInt(hour, 10);\n  if (isNaN(hourInt) || hourInt  23) {\n    await say('Hour must be a number between 0 and 23.');\n    return;\n  }\n  \n  \/\/ Convert day of week to cron format\n  const dayMap = {\n    'Sunday': 0,\n    'Monday': 1,\n    'Tuesday': 2,\n    'Wednesday': 3,\n    'Thursday': 4,\n    'Friday': 5,\n    'Saturday': 6\n  };\n  \n  const cronDay = dayMap[dayOfWeek];\n  \n  \/\/ Create cron schedule for the report\n  const cronSchedule = `0 ${hourInt} * * ${cronDay}`;\n  \n  \/\/ Generate a unique job ID\n  const jobId = `report_${environmentId}_${Date.now()}`;\n  \n  \/\/ Schedule the job\n  try {\n    const job = schedule.scheduleJob(cronSchedule, async function() {\n      \/\/ Generate and send the report\n      await generateWeeklyReport(environmentId, command.channel_id);\n    });\n    \n    \/\/ Store the job for later cancellation\n    scheduledJobs[jobId] = {\n      job,\n      taskType: 'report',\n      environmentId,\n      cronSchedule,\n      dayOfWeek,\n      hour: hourInt,\n      userId: command.user_id,\n      createdAt: new Date().toISOString()\n    };\n    \n    await say(`\u2705 Weekly report scheduled!\n*Environment:* `${environmentId}`\n*Schedule:* Every ${dayOfWeek} at ${hourInt}:00\n*Job ID:* `${jobId}`\n\nTo cancel this report, use `\/cancel_task ${jobId}``);\n  } catch (error) {\n    console.error('Error scheduling report:', error);\n    await say(`\u274c Error scheduling report: ${error.message}`);\n  }\n});\n\n\/\/ Function to generate weekly report\nasync function generateWeeklyReport(environmentId, channelId) {\n  try {\n    \/\/ Get environment details\n    const response = await kinstaRequest(`\/sites\/environments\/${environmentId}`);\n    \n    if (!response || !response.site || !response.site.environments || !response.site.environments.length) {\n      await web.chat.postMessage({\n        channel: channelId,\n        text: `\u26a0\ufe0f Weekly Report Error: No environment found with ID: `${environmentId}``\n      });\n      return;\n    }\n    \n    const env = response.site.environments[0];\n    \n    \/\/ Get backups for the past week\n    const backupsResponse = await kinstaRequest(`\/sites\/environments\/${environmentId}\/backups`);\n    \n    let backupsCount = 0;\n    let latestBackup = null;\n    \n    if (backupsResponse && backupsResponse.environment && backupsResponse.environment.backups) {\n      const oneWeekAgo = new Date();\n      oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);\n      \n      const recentBackups = backupsResponse.environment.backups.filter(backup =&gt; {\n        const backupDate = new Date(backup.created_at);\n        return backupDate &gt;= oneWeekAgo;\n      });\n      \n      backupsCount = recentBackups.length;\n      \n      if (recentBackups.length &gt; 0) {\n        latestBackup = recentBackups.sort((a, b) =&gt; b.created_at - a.created_at)[0];\n      }\n    }\n    \n    \/\/ Get environment status\n    const statusEmoji = env.is_blocked ? '\ud83d\udd34' : '\ud83d\udfe2';\n    const statusText = env.is_blocked ? 'Blocked' : 'Running';\n    \n    \/\/ Create report message\n    const reportDate = new Date().toLocaleDateString('en-US', {\n      weekday: 'long',\n      year: 'numeric',\n      month: 'long',\n      day: 'numeric'\n    });\n    \n    const reportMessage = `\ud83d\udcca *Weekly Report - ${reportDate}*\n*Site:* ${env.display_name}\n*Environment ID:* `${environmentId}`\n\n*Status Summary:*\n\u2022 Current Status: ${statusEmoji} ${statusText}\n\u2022 PHP Version: ${env.container_info?.php_engine_version || 'Unknown'}\n\u2022 Primary Domain: ${env.primaryDomain?.name || env.domains?.[0]?.name || 'N\/A'}\n\n*Backup Summary:*\n\u2022 Total Backups (Last 7 Days): ${backupsCount}\n\u2022 Latest Backup: ${latestBackup ? new Date(latestBackup.created_at).toLocaleString() : 'N\/A'}\n\u2022 Latest Backup Type: ${latestBackup ? latestBackup.type : 'N\/A'}\n\n*Recommendations:*\n\u2022 ${backupsCount === 0 ? '\u26a0\ufe0f No recent backups found. Consider creating a manual backup.' : '\u2705 Regular backups are being created.'}\n\u2022 ${env.is_blocked ? '\u26a0\ufe0f Site is currently blocked. Check for issues.' : '\u2705 Site is running normally.'}\n\n_This is an automated report. For detailed information, use the `\/site_status ${environmentId}` command._`;\n    \n    await web.chat.postMessage({\n      channel: channelId,\n      text: reportMessage\n    });\n  } catch (error) {\n    console.error('Report generation error:', error);\n    await web.chat.postMessage({\n      channel: channelId,\n      text: `\u274c Error generating weekly report: ${error.message}`\n    });\n  }\n}<\/code><\/pre>\n<h2>Gestione degli errori e monitoraggio<\/h2>\n<p>Quando il nostro bot inizia a eseguire operazioni reali come la modifica degli ambienti o l&#8217;attivazione di attivit\u00e0 programmate, c&#8217;\u00e8 bisogno di qualcosa di pi\u00f9 di <code>console.log()<\/code> per tenere traccia di ci\u00f2 che accade dietro le quinte.<\/p>\n<p>Vediamo di suddividere il tutto in livelli puliti e manutenibili:<\/p>\n<h3>Registrazione strutturata con Winston<\/h3>\n<p>Invece di stampare i registri nella console, usiamo il comando <code><a href=\"https:\/\/www.npmjs.com\/package\/winston\" target=\"_blank\" rel=\"noopener noreferrer\">winston<\/a><\/code> per inviare log strutturati a file e, facoltativamente, a servizi come <a href=\"https:\/\/www.loggly.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Loggly<\/a> o <a href=\"https:\/\/www.datadoghq.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Datadog<\/a>. Installiamolo con il seguente comando:<\/p>\n<pre><code class=\"language-bash\">npm install winston<\/code><\/pre>\n<p>Quindi configuriamo <code>logger.js<\/code>:<\/p>\n<pre><code class=\"language-js\">const winston = require('winston');\nconst fs = require('fs');\nconst path = require('path');\n\nconst logsDir = path.join(__dirname, '..\/logs');\nif (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir);\n\nconst logger = winston.createLogger({\n  level: 'info',\n  format: winston.format.combine(\n    winston.format.timestamp(),\n    winston.format.json()\n  ),\n  defaultMeta: { service: 'wordpress-slack-bot' },\n  transports: [\n    new winston.transports.Console({ format: winston.format.simple() }),\n    new winston.transports.File({ filename: path.join(logsDir, 'error.log'), level: 'error' }),\n    new winston.transports.File({ filename: path.join(logsDir, 'combined.log') })\n  ]\n});\n\nmodule.exports = logger;<\/code><\/pre>\n<p>Quindi, nel nostro file <code>app.js<\/code>, sostituiamo le chiamate a <code>console.log<\/code> o <code>console.error<\/code> con:<\/p>\n<pre><code class=\"language-js\">const logger = require('.\/logger');\n\nlogger.info('Cache clear initiated', { userId: command.user_id });\nlogger.error('API failure', { error: err.message });<\/code><\/pre>\n<h3>Inviare avvisi agli amministratori via Slack<\/h3>\n<p>Abbiamo gi\u00e0 la variabile env <code>ADMIN_USERS<\/code>. La useremo per avvisare il nostro team direttamente su Slack quando qualcosa di critico non funziona:<\/p>\n<pre><code class=\"language-js\">async function alertAdmins(message, metadata = {}) {\n  for (const userId of ADMIN_USERS) {\n    const dm = await web.conversations.open({ users: userId });\n    const channel = dm.channel.id;\n\n    let alert = `\ud83d\udea8 *${message}*n`;\n    for (const [key, value] of Object.entries(metadata)) {\n      alert += `\u2022 *${key}:* ${value}n`;\n    }\n\n    await web.chat.postMessage({ channel, text: alert });\n  }\n}<\/code><\/pre>\n<p>La utilizziamo in questo modo:<\/p>\n<pre><code class=\"language-js\">await alertAdmins('Backup Failed', {\n  environmentId,\n  error: error.message,\n  user: ``\n});<\/code><\/pre>\n<h3>Tracciare le prestazioni con indicatori di base<\/h3>\n<p>Se vogliamo solo sapere quanto \u00e8 in salute il nostro bot, non dobbiamo fare il passo pi\u00f9 lungo della gamba con <a href=\"https:\/\/prometheus.io\/docs\/guides\/query-log\/\" target=\"_blank\" rel=\"noopener noreferrer\">Prometheus<\/a>. Manteniamo un leggero oggetto delle performance:<\/p>\n<pre><code class=\"language-js\">const metrics = {\n  apiCalls: 0,\n  errors: 0,\n  commands: 0,\n  totalTime: 0,\n  get avgResponseTime() {\n    return this.apiCalls === 0 ? 0 : this.totalTime \/ this.apiCalls;\n  }\n};<\/code><\/pre>\n<p>Aggiorniamolo all&#8217;interno del nostro helper <code>kinstaRequest()<\/code>:<\/p>\n<pre><code class=\"language-js\">const start = Date.now();\ntry {\n  metrics.apiCalls++;\n  const res = await fetch(...);\n  return await res.json();\n} catch (err) {\n  metrics.errors++;\n  throw err;\n} finally {\n  metrics.totalTime += Date.now() - start;\n}<\/code><\/pre>\n<p>Esponiamolo tramite un comando come <code>\/bot_performance<\/code>:<\/p>\n<pre><code class=\"language-js\">app.command('\/bot_performance', async ({ command, ack, say }) =&gt; {\n  await ack();\n\n  if (!ADMIN_USERS.includes(command.user_id)) {\n    return await say('\u26d4 Not authorized.');\n  }\n\n  const msg = `\ud83d\udcca *Bot Metrics*\n\u2022 API Calls: ${metrics.apiCalls}\n\u2022 Errors: ${metrics.errors}\n\u2022 Avg Response Time: ${metrics.avgResponseTime.toFixed(2)}ms\n\u2022 Commands Run: ${metrics.commands}`;\n\n  await say(msg);\n});<\/code><\/pre>\n<h3>Opzionale: Definire le fasi di recupero<\/h3>\n<p>Se decidiamo di implementare una logica di recupero (come riprovare la cancellazione della cache via <a href=\"https:\/\/kinsta.com\/it\/blog\/come-utilizzare-ssh\/\">SSH<\/a>), possiamo creare un helper come:<\/p>\n<pre><code class=\"language-js\">async function attemptRecovery(environmentId, issue) {\n  logger.warn('Attempting recovery', { environmentId, issue });\n\n  if (issue === 'cache_clear_failure') {\n    \/\/ fallback logic here\n  }\n\n  \/\/ Return a recovery status object\n  return { success: true, message: 'Fallback ran.' };\n}<\/code><\/pre>\n<p>Teniamolo fuori dalla logica del comando principale, a meno che non si tratti di un percorso critico. In molti casi, \u00e8 meglio registrare l&#8217;errore, avvisare gli amministratori e lasciare che siano gli umani a decidere cosa fare.<\/p>\n<h2>Distribuire e gestire lo Slackbot<\/h2>\n<p>Una volta che il nostro bot \u00e8 completo di tutte le funzionalit\u00e0, dobbiamo distribuirlo in un ambiente di produzione dove possa funzionare 24\/7.<\/p>\n<p><a href=\"https:\/\/sevalla.com\/\" target=\"_blank\" rel=\"noopener noreferrer\">Sevalla<\/a> di Kinsta \u00e8 un luogo eccellente per l&#8217;hosting di questo tipo di bot. Supporta le applicazioni Node.js, le variabili d&#8217;ambiente, il logging e le distribuzioni scalabili.<\/p>\n<p>In alternativa, si pu\u00f2 containerizzare il bot utilizzando <a href=\"https:\/\/kinsta.com\/it\/blog\/cosa-e-docker\/\">Docker<\/a> o distribuirlo su qualsiasi piattaforma cloud che supporti Node.js e i servizi in background.<\/p>\n<p>Ecco alcune cose da tenere a mente prima di iniziare:<\/p>\n<ul>\n<li>Usiamo le variabili d&#8217;ambiente per tutti i segreti (token Slack, chiavi API Kinsta, chiavi SSH).<\/li>\n<li>Impostiamo la registrazione e il monitoraggio dell&#8217;attivit\u00e0 in modo da sapere quando qualcosa si interrompe.<\/li>\n<li>Eseguiamo il nostro bot con un gestore di processi come PM2 o la policy <code>restart: always<\/code> di Docker per mantenerlo in vita anche dopo i crash o i riavvii.<\/li>\n<li>Teniamo al sicuro le chiavi SSH, soprattutto se le utilizziamo per l&#8217;automazione.<\/li>\n<\/ul>\n<h2>Riepilogo<\/h2>\n<p>Abbiamo trasformato il nostro Slackbot da un semplice gestore di comandi a un potente strumento con interattivit\u00e0 reale, automazione programmata e un solido monitoraggio. Queste caratteristiche rendono il nostro bot pi\u00f9 utile, pi\u00f9 affidabile e molto pi\u00f9 piacevole da usare, soprattutto per i team che gestiscono pi\u00f9 siti WordPress.<\/p>\n<p>E se a questo si aggiunge la potenza dell&#8217;<a href=\"https:\/\/kinsta.com\/it\/docs\/kinsta-api\/\">API<\/a> e dell&#8217;<a href=\"https:\/\/kinsta.com\/it\/hosting-wordpress\/\">hosting di Kinsta<\/a>, si ha una configurazione scalabile e affidabile.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Gli Slackbot non devono rimanere in attesa che vengano digitati i comandi. Con la configurazione giusta, un bot pu\u00f2 permettere di gestire i siti WordPress con &#8230;<\/p>\n","protected":false},"author":287,"featured_media":80986,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kinsta_gated_content":false,"_kinsta_gated_content_redirect":"","footnotes":""},"tags":[],"topic":[26203],"class_list":["post-80985","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","topic-node-js"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v24.6 (Yoast SEO v24.6) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Aggiungere interattivit\u00e0 negli slackbot per la gestione di WordPress<\/title>\n<meta name=\"description\" content=\"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l&#039;API di Kinsta.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\" \/>\n<meta property=\"og:locale\" content=\"it_IT\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress\" \/>\n<meta property=\"og:description\" content=\"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l&#039;API di Kinsta.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\" \/>\n<meta property=\"og:site_name\" content=\"Kinsta\u00ae\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/kinstaitalia\/\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-16T07:44:22+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-05-19T07:24:18+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1470\" \/>\n\t<meta property=\"og:image:height\" content=\"735\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Joel Olawanle\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l&#039;API di Kinsta.\" \/>\n<meta name=\"twitter:image\" content=\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\" \/>\n<meta name=\"twitter:creator\" content=\"@olawanle_joel\" \/>\n<meta name=\"twitter:site\" content=\"@Kinsta_IT\" \/>\n<meta name=\"twitter:label1\" content=\"Scritto da\" \/>\n\t<meta name=\"twitter:data1\" content=\"Joel Olawanle\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tempo di lettura stimato\" \/>\n\t<meta name=\"twitter:data2\" content=\"11 minuti\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\"},\"author\":{\"name\":\"Joel Olawanle\",\"@id\":\"https:\/\/kinsta.com\/it\/#\/schema\/person\/efa7de30245ca15be5ce1dcacff89c07\"},\"headline\":\"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress\",\"datePublished\":\"2025-05-16T07:44:22+00:00\",\"dateModified\":\"2025-05-19T07:24:18+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\"},\"wordCount\":1907,\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/it\/#organization\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\",\"inLanguage\":\"it-IT\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\",\"url\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\",\"name\":\"Aggiungere interattivit\u00e0 negli slackbot per la gestione di WordPress\",\"isPartOf\":{\"@id\":\"https:\/\/kinsta.com\/it\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\",\"datePublished\":\"2025-05-16T07:44:22+00:00\",\"dateModified\":\"2025-05-19T07:24:18+00:00\",\"description\":\"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l'API di Kinsta.\",\"breadcrumb\":{\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#breadcrumb\"},\"inLanguage\":\"it-IT\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"it-IT\",\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage\",\"url\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\",\"contentUrl\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png\",\"width\":1470,\"height\":735},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/kinsta.com\/it\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Node.js\",\"item\":\"https:\/\/kinsta.com\/it\/argomenti\/node-js\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/kinsta.com\/it\/#website\",\"url\":\"https:\/\/kinsta.com\/it\/\",\"name\":\"Kinsta\u00ae\",\"description\":\"Soluzioni di hosting premium, veloci e sicure\",\"publisher\":{\"@id\":\"https:\/\/kinsta.com\/it\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/kinsta.com\/it\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"it-IT\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/kinsta.com\/it\/#organization\",\"name\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/it\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"it-IT\",\"@id\":\"https:\/\/kinsta.com\/it\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2023\/12\/kinsta-logo.jpeg\",\"contentUrl\":\"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2023\/12\/kinsta-logo.jpeg\",\"width\":500,\"height\":500,\"caption\":\"Kinsta\"},\"image\":{\"@id\":\"https:\/\/kinsta.com\/it\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/kinstaitalia\/\",\"https:\/\/x.com\/Kinsta_IT\",\"https:\/\/www.instagram.com\/kinstahosting\/\",\"https:\/\/www.linkedin.com\/company\/kinsta\/\",\"https:\/\/www.pinterest.com\/kinstahosting\/\",\"https:\/\/www.youtube.com\/c\/Kinsta\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/kinsta.com\/it\/#\/schema\/person\/efa7de30245ca15be5ce1dcacff89c07\",\"name\":\"Joel Olawanle\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"it-IT\",\"@id\":\"https:\/\/kinsta.com\/it\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/051bf577ce2c837846a1db9eef184758?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/051bf577ce2c837846a1db9eef184758?s=96&d=mm&r=g\",\"caption\":\"Joel Olawanle\"},\"description\":\"Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 300 technical articles majorly around JavaScript and it's frameworks.\",\"sameAs\":[\"https:\/\/joelolawanle.com\/\",\"https:\/\/www.linkedin.com\/in\/olawanlejoel\/\",\"https:\/\/x.com\/olawanle_joel\",\"https:\/\/www.youtube.com\/@joelolawanle\"],\"gender\":\"male\",\"knowsAbout\":[\"JavaScript\",\"React\",\"Next.js\"],\"knowsLanguage\":[\"English\"],\"jobTitle\":\"Technical Editor\",\"worksFor\":\"Kinsta\",\"url\":\"https:\/\/kinsta.com\/it\/blog\/author\/joelolawanle\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Aggiungere interattivit\u00e0 negli slackbot per la gestione di WordPress","description":"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l'API di Kinsta.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/","og_locale":"it_IT","og_type":"article","og_title":"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress","og_description":"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l'API di Kinsta.","og_url":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/","og_site_name":"Kinsta\u00ae","article_publisher":"https:\/\/www.facebook.com\/kinstaitalia\/","article_published_time":"2025-05-16T07:44:22+00:00","article_modified_time":"2025-05-19T07:24:18+00:00","og_image":[{"width":1470,"height":735,"url":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","type":"image\/png"}],"author":"Joel Olawanle","twitter_card":"summary_large_image","twitter_description":"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l'API di Kinsta.","twitter_image":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","twitter_creator":"@olawanle_joel","twitter_site":"@Kinsta_IT","twitter_misc":{"Scritto da":"Joel Olawanle","Tempo di lettura stimato":"11 minuti"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#article","isPartOf":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/"},"author":{"name":"Joel Olawanle","@id":"https:\/\/kinsta.com\/it\/#\/schema\/person\/efa7de30245ca15be5ce1dcacff89c07"},"headline":"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress","datePublished":"2025-05-16T07:44:22+00:00","dateModified":"2025-05-19T07:24:18+00:00","mainEntityOfPage":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/"},"wordCount":1907,"publisher":{"@id":"https:\/\/kinsta.com\/it\/#organization"},"image":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","inLanguage":"it-IT"},{"@type":"WebPage","@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/","url":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/","name":"Aggiungere interattivit\u00e0 negli slackbot per la gestione di WordPress","isPartOf":{"@id":"https:\/\/kinsta.com\/it\/#website"},"primaryImageOfPage":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage"},"image":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage"},"thumbnailUrl":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","datePublished":"2025-05-16T07:44:22+00:00","dateModified":"2025-05-19T07:24:18+00:00","description":"Automatizza la gestione di WordPress con un potente Slackbot. Aggiungi interattivit\u00e0, programmazione e monitoraggio con Node.js e l'API di Kinsta.","breadcrumb":{"@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#breadcrumb"},"inLanguage":"it-IT","potentialAction":[{"@type":"ReadAction","target":["https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/"]}]},{"@type":"ImageObject","inLanguage":"it-IT","@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#primaryimage","url":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","contentUrl":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2025\/05\/implement-interactivity-scheduling-and-monitoring-in-slackbots-for-managing-wordpress-sites.png","width":1470,"height":735},{"@type":"BreadcrumbList","@id":"https:\/\/kinsta.com\/it\/blog\/aggiungere-interattivita-programmazione-monitoraggio-slackbot\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/kinsta.com\/it\/"},{"@type":"ListItem","position":2,"name":"Node.js","item":"https:\/\/kinsta.com\/it\/argomenti\/node-js\/"},{"@type":"ListItem","position":3,"name":"Implementare interattivit\u00e0, programmazione e monitoraggio negli slackbot per la gestione di WordPress"}]},{"@type":"WebSite","@id":"https:\/\/kinsta.com\/it\/#website","url":"https:\/\/kinsta.com\/it\/","name":"Kinsta\u00ae","description":"Soluzioni di hosting premium, veloci e sicure","publisher":{"@id":"https:\/\/kinsta.com\/it\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/kinsta.com\/it\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"it-IT"},{"@type":"Organization","@id":"https:\/\/kinsta.com\/it\/#organization","name":"Kinsta","url":"https:\/\/kinsta.com\/it\/","logo":{"@type":"ImageObject","inLanguage":"it-IT","@id":"https:\/\/kinsta.com\/it\/#\/schema\/logo\/image\/","url":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2023\/12\/kinsta-logo.jpeg","contentUrl":"https:\/\/kinsta.com\/it\/wp-content\/uploads\/sites\/2\/2023\/12\/kinsta-logo.jpeg","width":500,"height":500,"caption":"Kinsta"},"image":{"@id":"https:\/\/kinsta.com\/it\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/kinstaitalia\/","https:\/\/x.com\/Kinsta_IT","https:\/\/www.instagram.com\/kinstahosting\/","https:\/\/www.linkedin.com\/company\/kinsta\/","https:\/\/www.pinterest.com\/kinstahosting\/","https:\/\/www.youtube.com\/c\/Kinsta"]},{"@type":"Person","@id":"https:\/\/kinsta.com\/it\/#\/schema\/person\/efa7de30245ca15be5ce1dcacff89c07","name":"Joel Olawanle","image":{"@type":"ImageObject","inLanguage":"it-IT","@id":"https:\/\/kinsta.com\/it\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/051bf577ce2c837846a1db9eef184758?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/051bf577ce2c837846a1db9eef184758?s=96&d=mm&r=g","caption":"Joel Olawanle"},"description":"Joel is a Frontend developer working at Kinsta as a Technical Editor. He is a passionate teacher with love for open source and has written over 300 technical articles majorly around JavaScript and it's frameworks.","sameAs":["https:\/\/joelolawanle.com\/","https:\/\/www.linkedin.com\/in\/olawanlejoel\/","https:\/\/x.com\/olawanle_joel","https:\/\/www.youtube.com\/@joelolawanle"],"gender":"male","knowsAbout":["JavaScript","React","Next.js"],"knowsLanguage":["English"],"jobTitle":"Technical Editor","worksFor":"Kinsta","url":"https:\/\/kinsta.com\/it\/blog\/author\/joelolawanle\/"}]}},"acf":[],"_links":{"self":[{"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/posts\/80985","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/users\/287"}],"replies":[{"embeddable":true,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/comments?post=80985"}],"version-history":[{"count":4,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/posts\/80985\/revisions"}],"predecessor-version":[{"id":80997,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/posts\/80985\/revisions\/80997"}],"alternate":[{"embeddable":true,"hreflang":"en","title":"English","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/en"},{"embeddable":true,"hreflang":"it","title":"Italian","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/it"},{"embeddable":true,"hreflang":"pt","title":"Portuguese","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/pt"},{"embeddable":true,"hreflang":"fr","title":"French","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/fr"},{"embeddable":true,"hreflang":"de","title":"German","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/de"},{"embeddable":true,"hreflang":"ja","title":"Japanese","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/jp"},{"embeddable":true,"hreflang":"nl","title":"Dutch","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/nl"},{"embeddable":true,"hreflang":"es","title":"Spanish","href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/translations\/es"},{"href":"https:\/\/kinsta.com\/it\/wp-json\/kinsta\/v1\/posts\/80985\/tree"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/media\/80986"}],"wp:attachment":[{"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/media?parent=80985"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/tags?post=80985"},{"taxonomy":"topic","embeddable":true,"href":"https:\/\/kinsta.com\/it\/wp-json\/wp\/v2\/topic?post=80985"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}