На нашем пути к тому, чтобы стать компанией, больше ориентированной на данные, мы в Netlify хотим убедиться, что у нас есть хорошие показатели от нашей службы поддержки. Таким образом, мы можем отслеживать, что люди спрашивают, почему они обращаются к нам, а также типы пользователей, которые пишут, чтобы внести более осознанные изменения в документы, пользовательский интерфейс и различные продукты, которые мы предлагаем. Более того, эти показатели помогут нам улучшить качество оказания поддержки. Ключевой частью получения достоверных данных от нашей службы поддержки было внесение более осознанных изменений в документы, пользовательский интерфейс и различные предлагаемые нами продукты. Более того, эти показатели помогут нам улучшить качество оказания поддержки. Проблема в том, что каждый должен помнить об этом.

Здесь, в Netlify, мы используем Intercom в качестве службы поддержки, и мы даже написали для него несколько пользовательских интеграций. Основная цель этого - расширить его функциональность за пределы API и веб-перехватчиков, которые уже предоставляет Intercom. Таким образом, мы можем автоматически получать релевантные данные по всем новым делам без необходимости вручную добавлять теги. В этом посте мы подробно рассмотрим, как мы применяем теги для обращений.

Мой технический стек для этого проекта довольно прост. Я использую лямбда-функцию Javascript, которая запускается через веб-перехватчик, когда кто-то закрывает разговор по внутренней связи. Интерком ожидает код ответа 200/204 в ответ на веб-перехватчик. В противном случае они предполагают, что это ошибка.

Интерком считает каждый ответ в разговоре «частью разговора». Каждая «часть беседы» включает в себя только один элемент диалога, автора, теги, а также любые вложения к нему. Их API закрытия беседы включает только часть беседы для последнего взаимодействия перед закрытием беседы. Это означает, что он будет включать теги только из этой части и не будет включать теги из более ранних частей этого разговора. В результате нам придется запросить их API, чтобы получить полный список тегов для беседы.

Единственная зависимость, которую я использую, - это Axios, который является HTTP-клиентом. Я предпочитаю Axios выборке по нескольким причинам, одна из которых - автоматический синтаксический анализ JSON, а другая - лучшая обработка ошибок (они выдают ошибки для ответов с кодом состояния, отличным от 200).

Это упрощенная временная шкала того, что происходит в этой функции:

Функция начинается с сохранения данных из веб-перехватчика в некоторые переменные, а также с моего токена API внутренней связи, который сохраняется в переменной среды:

const eventBody = JSON.parse(event.body);
const { assignee } = eventBody.data.item;
const intercomHeaders = {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${process.env.INTERCOM_SECRET}`,
};

Все это делается внутри функции-обработчика лямбда-выражений, отсюда и происходитevent. event включает полезную нагрузку из веб-перехватчика, поэтому мы извлекаем человека, которому назначен разговор, чтобы использовать его позже в функции. Я также объявляю заголовки, которые буду использовать в своих HTTP-запросах к Intercom API.

Поскольку не каждый разговор должен обрабатываться одинаково, я создал несколько ответов и сохранил их в переменных. Чтобы понять тело ответа, вам, вероятно, следует прочитать API ответа Intercom. Но самая важная часть:

Вот ответы, которые я создал для наших вариантов использования:

const noteBodyOpen = {
type: 'admin',
message_type: 'open',
admin_id: '1669197',
};
const noteBodyComment = {
type: 'admin',
message_type: 'note',
admin_id: '1669197',
body: `Looks like a human forgot to tag this conversation.
You can find info on how we categorize tickets at https://internal-docs.netlify.com/meta/ticket-categorization/`,
};

Все они являются ответами «администратора», что означает, что они исходят от учетной записи администратора, а не учетной записи пользователя. admin_id, который мы используем для всех автоматических ответов, - это пользователь-бот, которого мы создали для этой цели. Мы делаем это, чтобы дать понять пользователям и администраторам, что они на самом деле являются автоматическими ответами. То же самое мы делаем и для личных заметок. message_type - это note, который сообщает внутренней связи, что это внутренние заметки, которые видны только администраторам, а не пользователям. body - это содержание заметки, которую видит администратор после того, как наша функция повторно открывает закрытый диалог.

На самом деле я также использую эту же функцию для переназначения дел, которые закрыты, находясь в неправильной очереди. Таким образом мы гарантируем, что закрытые дела вернутся в надлежащую очередь и не потеряны. Однако это выходит за рамки данной статьи, поэтому я удалил разделы, в которых это делается.

Затем мы определяем метод, который реагирует на веб-перехватчик, чтобы интерком узнал, что мы его получили и не сработали. В этом случае мы всегда возвращаем 200.

const respond = () => {
callback(null, {
statusCode: 200,
body: JSON.stringify('I got it!'),
});
};

Затем мы определяем метод отправки POST-запросов к Intercom API. Полезные данные для запроса передаются в метод, поскольку они различаются в зависимости от того, что мы пытаемся сделать. Просто пытаюсь остаться здесь СУХИМ.

Еще одно замечание: я ничего не делаю с ответом на запрос POST. Функция запускается в ответ на веб-перехватчик, она довольно проста и больше ничего не делает, поэтому нет никакой реальной обработки ошибок, кроме записи ошибки в консоль.

const pingIntercom = async body => {
try {
const _ = await axios.post(
`https://api.intercom.io/conversations/${eventBody.data.item.id}/reply`,
JSON.stringify(body),
{ headers: intercomHeaders }
);
respond();
} catch (err) {
console.log(err);
respond();
}
};

Этот следующий метод вызывается с массивом тегов в качестве аргумента. Логика в нем специфична для нашего варианта использования, но он проверяет, назначен ли диалог нашей платной очереди или неназначенной очереди. Он также проверяет, есть ли в массиве тегов элемент в первой позиции (чтобы увидеть, есть ли какие-либо теги в массиве тегов). Его также можно было переписать, чтобы проверить длину массива.

Хотя эту функцию нельзя точно скопировать из-за того, что имена относятся к нашему рабочему процессу, ее логику можно использовать, чтобы убедиться, что вы применяете теги только в определенных почтовых ящиках. Если у вас есть другие люди в компании, использующие Intercom по-разному и не требующие принудительного применения тегов, то вы не хотите, чтобы все их разговоры снова открывались!

const replyToConvo = tags => {
if ((assignee.name === 'Paid customers' || assignee.type === 'nobody_admin') && !tags[0]) {
pingIntercom(noteBodyOpen);
pingIntercom(noteBodyComment);
}
respond();
};

Подводя итог всему этому, у нас есть последняя функция, которая вызывается первой. Его цель - получить полный список тегов, поскольку это необходимо, прежде чем мы сможем что-либо сделать с беседой.

const getTagList = async () => {
try {
const response = await axios.get(`https://api.intercom.io/conversations/${eventBody.data.item.id}`, {
headers: intercomHeaders,
});
console.log(response.data.tags.tags);
replyToConvo(response.data.tags.tags);
} catch (err) {
console.log('there was a problem fetching the full conversation');
console.log(err);
}
};
getTagList();

Мы вызываем его сразу после того, как он определен, чтобы мы могли преобразовать его в IIFE:

(async () => {
try {
const response = await axios.get(`${BASE_URL}/${eventBody.data.item.id}`, {
headers: intercomHeaders,
});
console.log(response.data.tags.tags);
replyToConvo(response.data.tags.tags);
} catch (err) {
console.log('there was a problem fetching the full conversation');
console.log(err);
}
})();

Готово! Но это не обязательно. Вы можете добавить дополнительные функции, например, принудительно закрывать беседы, находясь в правильном почтовом ящике. Например, мы открываем беседы, чтобы пригласить людей в бета-программы или отделить разговоры, связанные с чрезвычайными ситуациями, от обычных очередей. Если вы не открываете эти очереди так часто, возможно, вы захотите убедиться, что разговоры никогда не закрываются, пока они находятся в них. Или, если вы хотите, чтобы закрытые разговоры были перемещены из личных очередей в неназначенную очередь, чтобы, если кто-то берет выходной, вы не теряете ответы на его разговоры, пока они не вернутся к работе. Есть много вариантов использования, и я хотел бы услышать некоторые из ваших!