Часть 3. Синхронизация данных — Salesforce и Slack

Наконец, мы подошли к заключительной части нашей серии статей об использовании Slack Starter Kit от Salesforce для быстрого создания развертываемого приложения Slack, взаимодействующего с данными Salesforce. Slack Starter Kit позволяет чрезвычайно легко пройти аутентификацию в Salesforce, организовать код в повторно используемые фрагменты и развернуть проект в Heroku для прямого доступа. Мы в основном основывали эту серию на этих видеоуроках, демонстрирующих, как создавать приложения Slack.

«В нашем первом посте мы познакомились со Slack Starter Kit и настроили нашу среду разработки. «В нашем втором посте наше приложение Slack отправило запрос на получение данных из организации Salesforce, а затем представило результат с компонентами пользовательского интерфейса из Block Kit. Теперь мы собираемся расширить этот шаблон, чтобы продемонстрировать, как вы можете полностью редактировать данные Salesforce в Slack. Давайте начнем!

Создание ярлыка

Помимо пользовательского интерфейса Block Kit, в Slack есть поддержка двух других систем интерактивности: Slash Commands и shortcuts. Команды с косой чертой вводятся пользователем в текстовом поле Slack (доступно в каждом канале), а ярлыки имеют графическую природу. Поскольку их легче визуализировать, мы продемонстрируем ярлыки, создав ярлык, который будет извлекать наш список контактов и давать нам возможность редактировать их.

Для добавления ярлыка (или команды косой черты, если на то пошло) сначала необходимо сообщить Slack имя команды. Перейдите на страницу обзора своего приложения в Slack и нажмите Интерактивность и ярлыки:

Нажмите Создать новый ярлык и выберите Глобальный в качестве типа ярлыка, затем нажмите Далее. На следующей странице введите следующие значения:

  • Имя: изменить контакты
  • Краткое описание: редактирует контакты на SFDC.
  • Идентификатор обратного вызова: edit-contact-shortcut

Нажмите Создать, затем нажмите Сохранить изменения. Переключитесь в рабочее пространство Slack и щелкните значок плюса в текстовой области. Вы сможете просматривать все ярлыки рабочего пространства. Здесь вы также увидите новый ярлык, который вы только что создали:

Этот ярлык еще ничего не делает, но этот процесс необходим, чтобы Slack узнал о вашем ярлыке. Далее давайте добавим код для обработки события, которое срабатывает всякий раз, когда кто-то нажимает на этот ярлык.

Подключение ярлыка

В редакторе кода перейдите к apps/slack-salesforce-starter-app/listeners/shortcuts/index.js. Это место, где мы связываем события быстрого доступа с исполняемым кодом. В Starter Kit уже есть один ярлык: whoami. Данная строка подсказывает нам, что нам нужно сделать: мы вызываем функцию с именем shortcut и передаем строку и имя функции. В этом случае наша строка — это идентификатор обратного вызова, который мы определили ранее, а имя нашей функции — это код, который нам еще предстоит написать. Измените содержимое файла, чтобы оно выглядело следующим образом:

const { whoamiCallback } = require('./whoami');
const { editContactCallback } = require('./edit-contact');
module.exports.register = (app) => {
   app.shortcut('who_am_i', whoamiCallback);
   app.shortcut('edit-contact-shortcut', editContactCallback);
};

Мы закладываем здесь основу, говоря: «Приложение Slack, если вы получите ярлык с идентификатором обратного вызова edit-contact-shortcut, запустите editContactCallback».

В этой же папке создайте файл с именем edit-contact.js и вставьте в него следующие строки:

'use strict';
const {
  editContactResponse,
  authorize_sf_prompt
} = require('../../user-interface/modals');
const editContactCallback = async ({ shortcut, ack, client, context }) => {
  try {
    await ack();
    if (context.hasAuthorized) {
      const conn = context.sfconnection;
      await client.views.open({
        trigger_id: shortcut.trigger_id,
        view: await editContactResponse(conn)
      });
    } else {
      // Get BotInfo
      const botInfo = await client.bots.info({ bot: context.botId });
      // Open a Modal with message to navigate to App Home for authorization
      await client.views.open({
        trigger_id: shortcut.trigger_id,
        view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
      });
    }
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }
};
module.exports = { editContactCallback };

Теперь это может показаться пугающим, но большая часть этого просто касается шаблона аутентификации, гарантирующего, что у пользователя есть активное соединение SFDC. В первом логическом пути (если context.hasAuthorized равно true) мы выполняем функцию с именем editContactResponse, которая принимает наше открытое действие Salesforce conn. В отрицательном случае мы просим пользователя перейти на вкладку «Главная» для повторной аутентификации, как мы это делали в первой части этого руководства.

Перейдите в папку apps/slack-salesforce-starter-app/user-interface/modals и создайте файл с именем edit-contact-response.js. Здесь мы откроем модальное окно с контактной информацией, похожее на строки, которые мы видели на вкладке «Главная» во второй части этого руководства:

'use strict';
const { Elements, Modal, Blocks } = require('slack-block-builder');
const editContactResponse = async (conn) => {
  const result = await conn.query(
    `Select Id, Name, Description FROM Contact`
  );
  let records = result.records;
  let blockCollection = records.map((record) => {
    return Blocks.Section({
      text: `*${record.Name}*\n${record.Description}`
    }).accessory(
      Elements.Button()
        .text(`Edit`)
        .actionId(`edit_contact`)
        .value(record.Id)
    );
  });
  return Modal({ title: 'Salesforce Slack App', close: 'Close' })
    .blocks(blockCollection)
    .buildToJSON();
};
module.exports = { editContactResponse };

Основное различие между кодом из части 2 и этим блоком заключается в том, что мы используем массив с именем blockCollection, что позволяет нам создавать массив блоков (в данном случае блоков Section). blocks знает, как взять этот массив и преобразовать его в формат, понятный Slack, что упрощает создание данных через циклический массив, как мы сделали здесь. Во второй части нашей серии мы создали гигантскую строку данных. Однако с помощью BlockCollection мы можем прикрепить другие элементы Slack, например кнопки, что мы и сделали здесь.

Наконец, в apps/slack-salesforce-starter-app/user-interface/modals/index.js нам нужно экспортировать эту функцию, чтобы ее можно было импортировать нашей функцией edit-contact.js:

'use strict';
const { whoamiresponse } = require('./whoami-response');
const { editContactResponse } = require('./edit-contact-response');
const { authorize_sf_prompt } = require('./authorize-sf-prompt');
module.exports = { whoamiresponse, editContactResponse, authorize_sf_prompt };

После того, как вы развернули этот новый код в Heroku с помощью git push, переключитесь на свое рабочее пространство Slack и попробуйте выполнить ярлык; вас встретит диалоговое окно, похожее на это:

Обновление данных Salesforce

Мы можем получать и отображать данные Salesforce. Теперь пришло время подключить кнопку «Изменить», чтобы изменить данные Salesforce!

Многие интерактивные компоненты Slack имеют action_id, который, как и callback_id, служит для идентификации элемента, с которым действовал пользователь. Как и все остальное в Starter Kit, есть специальный каталог, в котором вы можете определить прослушиватели для этих идентификаторов действий: apps/slack-salesforce-starter-app/listeners/actions. В файле index.js давайте добавим новую строку, которая связывает идентификатор действия с нашим еще не написанным функционалом:

'use strict';
const { appHomeAuthorizeButtonCallback } = require('./app-home-authorize-btn');
const { editContactButtonCallback } = require('./edit-contact-btn');
module.exports.register = (app) => {
  app.action('authorize-with-salesforce', appHomeAuthorizeButtonCallback);
  app.action('edit_contact', editContactButtonCallback);
};

В этой же папке создайте новый файл с именем edit-contact-btn.js и вставьте в него следующие строки:

'use strict';
const {
  editIndividualContact,
  authorize_sf_prompt
} = require('../../user-interface/modals');
const editContactButtonCallback = async ({ body, ack, client, context }) => {
  const contactId = body.actions[0].value;
  try {
    await ack();
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }
  if (context.hasAuthorized) {
    const conn = context.sfconnection;
    const result = await conn.query(
      `SELECT Id, Name, Description FROM Contact WHERE Id='${contactId}'`
    );
    let record = result.records[0];
    await client.views.push({
      trigger_id: body.trigger_id,
      view: editIndividualContact(record)
    });
  } else {
    // Get BotInfo
    const botInfo = await client.bots.info({ bot: context.botId });
    // Open a Modal with message to navigate to App Home for authorization
    await client.views.push({
      trigger_id: body.trigger_id,
      view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
    });
  }
};
module.exports = { editContactButtonCallback };

Начало и конец этого файла должны выглядеть знакомыми: мы отправляем ответ ack обратно в Slack, чтобы сообщить ему, что наше приложение получило полезную нагрузку события (в данном случае от нажатия кнопки «Изменить»). Мы также проверяем, аутентифицированы ли мы. Здесь мы выполняем один поиск в БД, используя идентификатор контакта, который мы прикрепили в качестве значения к кнопке «Изменить» при создании нашего пользовательского интерфейса.

Этот фрагмент кода создает еще один модальный дизайн, который нам нужно определить. Вернувшись в apps/slack-salesforce-starter-app/user-interface/modals, создайте файл с именем edit-individual-contact.js и вставьте в него следующие строки:

'use strict';
const { Elements, Modal, Blocks } = require('slack-block-builder');
const editIndividualContact = (record) => {
  return Modal({ title: 'Edit Contact', close: 'Close' })
    .blocks(
      Blocks.Input({ blockId: 'description-block', label: record.Name }).element(
        Elements.TextInput({
          placeholder: record.Description,
          actionId: record.Id
        })
     )
   )
   .submit('Save')
   .callbackId('edit-individual')
   .buildToJSON();
};
module.exports = { editIndividualContact };

Здесь мы создали модальное окно с одним блоком: элементом ввода. Элемент будет предварительно заполнен описанием контакта. Мы можем отредактировать этот блок и изменить описание на все, что захотим.

В этом фрагменте кода есть два важных замечания:

  1. Обратите внимание, что мы присоединяем actionId к элементу ввода. Это аналогично идентификатору, который мы прикрепили к кнопке «Изменить» ранее, за исключением того, что на этот раз он генерируется динамически на основе идентификатора записи, которую мы редактируем.
  2. Вы также заметите, что у нас есть еще один идентификатор, callbackID, который прикреплен к модальному модулю. Помните о существовании этих идентификаторов: мы рассмотрим их оба через мгновение. А пока откройте файл index.js в том же каталоге и require/export эту новую функцию создания модальных окон:
const { editIndividualContact } = require('./edit-individual-contact');
// ...
module.exports = {
  whoamiresponse,
  editContactResponse,
  editIndividualContact,
  authorize_sf_prompt
};

Теперь, когда вы нажмете кнопку «Изменить», вам будет предложено изменить описание:

Теперь нам нужно отправить этот обновленный текст в Salesforce. Нажмите кнопку Сохранить и… ничего не произойдет. Почему? Что ж, в Slack есть другой набор событий для подобных взаимодействий, который называется просмотр представлений. Starter Kit обеспечивает хорошую отправную точку при создании приложения, но он не подходит для всех случаев использования Slack, включая этот. Но это не проблема — мы сами добавим функционал!

Внутри apps/slack-salesforce-starter-app/user-interface folder создайте новую папку с именем views. Как и раньше, у нашей кнопки Сохранить здесь есть идентификатор действия для ее идентификации: edit-individual-contact. Вернемся к apps/slack-salesforce-starter-app/listeners/actions/index.js, чтобы настроить это как функцию:

const { editIndividualButtonCallback } = require('./edit-individual-btn);
// ... 
app.action('edit-individual-contact', editIndividualButtonCallback);

Создайте новый файл с именем edit-individual-contact.js и вставьте в него следующие строки:

'use strict';
const { submitEditCallback } = require('./submit-edit');
module.exports.register = (app) => {
  app.view('edit-individual', submitEditCallback);
};

Этот формат идентичен другим слушателям, предоставляемым Slack Starter Kit. Единственное отличие состоит в том, что мы вызываем метод view. Нам также необходимо зарегистрировать этот слушатель вместе с другими. Откройте apps/slack-salesforce-starter-app/listeners/index.js и require/register новый слушатель представления:

const viewListener = require('./views');
module.exports.registerListeners = (app) => {
  // ...
  viewListener.register(app);
};

Затем в apps/slack-salesforce-starter-app/listeners/views создайте еще один файл с именем submit-edit.js и вставьте в него следующие строки:

'use strict';
const { editContactResponse, authorize_sf_prompt } = require('../../user-interface/modals');
const submitEditCallback = async ({ view, ack, client, context }) => {
  try {
    await ack();
  } catch (error) {
    // eslint-disable-next-line no-console
    console.error(error);
  }
  if (context.hasAuthorized) {
    const contactId = view.blocks[0].element.action_id;
    const newDescription =
      view.state.values['description-block'][contactId].value;
    const conn = context.sfconnection;
    await conn.sobject('Contact').update({
      Id: contactId,
      Description: newDescription
    });
    await client.views.open({
      trigger_id: view.trigger_id,
      view: await editContactResponse(conn)
    });
  } else {
    // Get BotInfo
    const botInfo = await client.bots.info({ bot: context.botId });
    // Open a Modal with message to navigate to App Home for authorization
    await client.views.push({
      trigger_id: view.trigger_id,
       view: authorize_sf_prompt(context.teamId, botInfo.bot.app_id)
    });
  }
};
module.exports = { submitEditCallback };

Давайте обсудим те идентификаторы, которые мы установили ранее. Когда Slack отправляет полезные данные событий в наше приложение, он автоматически генерирует идентификатор для каждого элемента ввода по умолчанию. Это потому, что Slack не знает основные данные. Вы несете ответственность за то, чтобы называть элементы с помощью идентификаторов действий. Slack использует эти идентификаторы в качестве ключей для заполнения полезной нагрузки. Когда вы получаете полезную нагрузку Slack, вы можете использовать предоставленные вами ключи для анализа данных, введенных пользователем.

Теперь, если вы выполните процесс редактирования описания вашего контакта, вы заметите, что модальное окно будет правильно сохранено. Чтобы убедиться, что данные на стороне Salesforce были обновлены, запустите sfdx force:org:open в своем терминале и перейдите на вкладку «Контакты».

Заключение

Slack Starter Kit упростил создание приложения Slack, которое прослушивает пользовательские события. Кроме того, это также делает взаимодействие с Salesforce и Heroku абсолютным удовольствием. Мы рассмотрели почти все, что может сделать Starter Kit. Если вы хотите узнать больше о том, как Slack и Salesforce могут работать вместе, ознакомьтесь с нашей записью в блоге Создаем лучше вместе. Кроме того, бэкэнд-фреймворк, взаимодействующий с Salesforce, — это замечательный проект JSforce. Обязательно ознакомьтесь с его документацией и Справочником по Salesforce API, чтобы узнать больше о том, что вы можете создать!