This is translation of Slack Next-gen Platform – Built-in Forms  written by Kaz (SDK Engineering & DevRel at Slack), I do not include any personal opinion here except guessing when meaning of original text is not clear for me)

이 글은 슬랙의 SDK Engineering / DevRel 인 Kaz 의 글을 번역한것입니다. 저의 개인 의견은 들어가지 않으며, 일부 원문의 의미가 애매한 경우에만 부연 설명을 달았습니다.


이번 튜토리얼 에서는 Slack 의 차세대 플랫폼에서 빌트인 되어있는 폼(양식) 을 어떻게 사용하는지 알아보겠습니다.

플랫폼 상에서 간단한 폼 기능을 제공하고 있는데요, 바로 빌트인 되어있는 Schema.slack.functions.OpenForm 펑션 입니다.

이 튜토리얼을 끝내고 나시면 빌트인 폼에 대한 전문가가 되어 계실 것입니다!

Prerequisites

차세대 플랫폼을 접하는 것이 처음이시라면, 이전의 튜토리얼인 “The Simplest Hello World” 부터 읽어주세요. 요약하면 유료 Slack 워크스페이스와 해당 워크스페이스에서 “beta feature” 를 사용할 수 있는 권한이 필요합니다. 가지고 계시다면 Slack CLI 와 워크스페이스를 연결하기만 하면 됩니다.

준비되었다면 앱을 한번 만들어보도록 하죠. 시작해보겠습니다.

Create a Blank Project

slack create 명령어를 사용하여, 새로운 프로젝트를 시작할 수 있습니다. 이 튜토리얼에서는 아무것도 없는 상태에서 앱을 만들어보겠습니다. “Blank Project” 를 선택하여 주십시요.

$ slack create
? Select a template to build from:

  Hello World
  A simple workflow that sends a greeting

  Scaffolded project
  A solid foundational project that uses a Slack datastore

> Blank project
  A, well.. blank project

  To see all available samples, visit github.com/slack-samples.

프로젝트가 생성되면, slack run 명령어가 정상적으로 동작하는지 확인해보세요. 이 명령어는 “dev” 버전의 앱을 워크스페이스에 설치합니다. 이 앱의 봇 유저가 생성되고, 이 봇은 API 호출을 위한 봇 토큰 값을 가지고 있습니다.

$ cd affectionate-panther-654
$ slack run
? Choose a workspace  seratch  T03E94MJU
   App is not installed to this workspace

Updating dev app install for workspace "Acme Corp"

  Outgoing domains
   No allowed outgoing domains are configured
   If your function makes network requests, you will need to allow the outgoing domains
   Learn more about upcoming changes to outgoing domains: https://api.slack.com/future/changelog
  seratch of Acme Corp
Connected, awaiting events

Connected, awaiting events 로그 메세지를 보신다면, 이 앱이 성공적으로 워크스페이스에 연결된 것입니다. “Ctrl +C” 를 눌러 로컬 앱 프로세스를 종료합니다.

Define Workflow and Trigger

간단한 데모 워크플로와 link 트리거를 정의하는 것부터 시작해보겠습니다. 빌트인 되어있는 폼을 사용하기 위해서는 반드시 link 트리거를 사용해야만 합니다. 그 이유는 OpenForm 펑션이 엔드유저와 상호작용을 하기 위해서 interactivity (Schema.slack.types.interactivity) 인풋 파라미터를 필요로 하기 때문입니다. 현재 시점 기준으로는 link 트리거만 이러한 입력값을 받아서 워크플로와 펑션에 전달할 수 있습니다.

대안으로는, 채널의 메세지내에서 버튼/선택 메뉴를 사용하여 워크플로 내에 interactivity 를 생성하는 방법이 있습니다. 이 경우 OpenForm 에서 필요로 하는 interactivity 를 제공할 수 있습니다. 그렇지만 이번 튜토리얼에서는 다루지 않겠습니다.

비어있는 워크플로와 트리거를 정의하는 것부터 시작하겠습니다. 늘 그래 왔듯이 workflow_and_trigger.ts 라는 이름으로 저장하겠습니다.

// ----------------
// Workflow Definition
// ----------------

import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
export const workflow = DefineWorkflow({
  callback_id: "form-demo-workflow",
  title: "OpenForm Demo Workflow",
  input_parameters: {
    properties: {
      interactivity: { type: Schema.slack.types.interactivity },
      user_id: { type: Schema.slack.types.user_id },
      channel_id: { type: Schema.slack.types.channel_id },
    },
    required: ["interactivity", "user_id", "channel_id"],
  },
});

// TODO: Add a step using the built-in form here

// TODO: Confirm the outputs from the above OpenForm function

// ----------------
// Trigger Definition
// ----------------

import { Trigger } from "deno-slack-api/types.ts";
const trigger: Trigger<typeof workflow.definition> = {
  type: "shortcut",
  name: "Form Demo Trigger",
  workflow: `#/workflows/${workflow.definition.callback_id}`,
  inputs: {
    // interactivity is necessary for using OpenForm function
    interactivity: { value: "{{data.interactivity}}" },
    // The following inputs are not necessary for OpenForm
    // You'll use this just for the succeeding functions,
    // which confirm the outputs of OpenForm
    user_id: { value: "{{data.user_id}}" },
    channel_id: { value: "{{data.channel_id}}" },
  },
};
export default trigger;

마찬가지로 manifest.ts 에 워크플로를 추가하여 줍니다.

import { Manifest } from "deno-slack-sdk/mod.ts";
// Add this
import { workflow as DemoWorkflow } from "./workflow_and_trigger.ts";

export default Manifest({
  name: "frosty-mink-263",
  description: "Demo workflow",
  icon: "assets/default_new_app_icon.png",
  workflows: [DemoWorkflow], // Add this
  outgoingDomains: [],
  botScopes: ["commands", "chat:write", "chat:write.public"],
});

Create a Link Trigger

두개의 터미널 윈도우를 열고 각각 slack runslack triggers create –trigger-def workflow_and_trigger.ts 명령어를 실행합니다. 다음과 같은 아웃풋을 볼 수 있습니다.

$ slack triggers create --trigger-def workflow_and_trigger.ts
? Choose an app  seratch (dev)  T03E94MJU
   frosty-mink-263 (dev) A04G9S43G2K

Trigger created
   Trigger ID:   Ft04HGLH426L
   Trigger Type: shortcut
   Trigger Name: Form Demo Trigger
   URL: https://slack.com/shortcuts/***/***

첫 튜토리얼에서 배우셨듯이, 이 link 트리거 주소를 공개 채널에 포스팅 하시면, 트리거를 사용할 준비가 된 것입니다. 코딩 파트로 돌아갑시다.

Add OpenForm Function to the Workflow

다음 두개의 펑션 스텝을 정의한 워크플로에 추가합니다.

// Step using the built-in form
const formStep = workflow.addStep(Schema.slack.functions.OpenForm, {
  title: "Send a greeting",
  interactivity: workflow.inputs.interactivity,
  submit_label: "Send greeting",
  fields: {
    // fields.elements will be converted to Block Kit components under the hood
    elements: [
      {
        name: "recipient",
        title: "Recipient",
        type: Schema.slack.types.user_id, // => "users_select"
        default: workflow.inputs.user_id,
      },
      {
        name: "channel",
        title: "Channel to send message to",
        type: Schema.slack.types.channel_id, // => "channels_select"
        default: workflow.inputs.channel_id,
      },
      {
        name: "message",
        title: "Message to recipient",
        type: Schema.types.string, // => "plain_text_input"
        long: true, // => multiline: true
      },
    ],
    required: ["recipient", "channel", "message"],
  },
});

// Confirm the outputs from the above OpenForm function
workflow.addStep(Schema.slack.functions.SendEphemeralMessage, {
  // The name of the element will be the key to access the value
  user_id: formStep.outputs.fields.recipient,
  channel_id: formStep.outputs.fields.channel,
  message: "OpenForm's `outputs.fields`: `" + formStep.outputs.fields + "`",
});

아직 Block Kit 은 익숙하지 않으실 것입니다. Block Kit 은 Slack 의 UI 프레임워크로, Block Kit 을 통해 JSON 데이터 포맷을 통해 멋진 인터페이스를 만드실 수 있습니다. 만들어 두시면 데스크톱 앱/브라우저/모바일 앱에서 다 동일하게 보입니다.

OpenForm 펑션은 이러한 UI 정의를 더 간단하게 만들어 둔것입니다. 모달 UI 를 정의할 때, OpenForm 펑션은 Block Kit 과 동일한 효과를 낼 수 있습니다.

Block Kit 에 대해서도 따로 튜토리얼을 만들어 보도록 하겠습니다.

Run The Workflow

워크플로를 실행해보죠. 실행하고 나면, 이 앱은 모달 팝업 입력대화창을 띄워서, 여러분들이 입력하는 메세지를 받고, OpenForm 의 형태를 가지고 여러분들에게만 보이는 메세지를 보냅니다.

More Form Options

이 문서에서 보신바와 같이, 폼의 기본 요소는 name, title, type, description 그리고 default 입니다. 일반적으로 최소한으로 필요로 하는 것은 name, title 그리고 type 입니다. 최대한 많은 예제 공유드려보도록 하겠습니다.

Required Inputs

위의 예제에서 required 리스트는 [“recipient”, “channel”, “message”] 입니다. 그 말은 이 3개의 입력값을 반드시 필요로 한다는 것입니다. fields.elementsname 이라면 무엇이든 추가할 수 있습니다.

Text Input Element

단순한 텍스트 입력폼의 구성요소는 다음처럼 보입니다. type 은 반드시 schema.types.string 이어야만 하고, 이 type 을 사용하면 OpenForm 에서 평문을 입력받는 블록 요소들을 만듭니다.

{
  name: "message", // unique key for this element
  title: "Message to recipient", // Label on the UI
  type: Schema.types.string, // => "plain_text_input"
},

만약 한줄이 아닌, 여러줄로 되어있는 입력을 받으려면, long: true 프로퍼티를 추가합니다. 또한, 입력값의 길이 제한을 위해서, minLengthmaxLength 를 사용할 수 있습니다.

{
  name: "message",
  title: "Message to recipient",
  type: Schema.types.string,
  long: true, // => multiline: true
  minLength: 5, // inclusive
  maxLength: 20, // inclusive
},

텍스트 입력값을 검증하는 다른 옵션은 format 을 사용하는 것입니다. email 이나 url 등의 format 을 사용할 수 있습니다.

{
  name: "contact",
  title: "Your Email Address",
  type: Schema.types.string,
  format: "email",
},

Static Select Menu

Schema.types.string 타입을 사용하면, 고정 옵션 아이템이 존재하는 선택 메뉴를 사용할 수 있습니다. (일종의 디폴트 값으로 보시면 되겠습니다.)

{
  name: "favorite",
  title: "Favorite",
  type: Schema.types.string,
  choices: [
    { value: "dog", title: "Dog" },
    { value: "cat", title: "Cat" },
  ],
  enum: ["dog", "cat"],
  default: "cat",
},

위 예제는 다음과 같이 보입니다.

enum 프로퍼티는 UI 상에서는 보이지 않기 때문에, 생략해도 되는거 아니야? 라고 생각하실 수 있는데요, 그래도 필요합니다. 만약 지우시게 되면 선택 메뉴가 아니라, 단순히 텍스트를 입력받는 폼으로 바뀌게 됩니다. choices 프로퍼티가 사용되지 않게 되는 것이죠.

Channel/User Select Menu

type 으로 user_id 를 사용하면 user select 필드값이 요소가 됩니다.

{
  name: "recipient",
  title: "Recipient",
  type: Schema.slack.types.user_id, // => "users_select"
},

유사하게 channel_idtype 으로 사용하면 channel select 의 필드값이 요소가 됩니다.

{
  name: "channel",
  title: "Channel to send message to",
  type: Schema.slack.types.channel_id, // => "channels_select"
},

Multi-select Menu

Block 은 다중 선택 메뉴 요소를 지원합니다. 이 요소를 사용할려면 Schema.types.arrayType으로 선택해서 쓰시면 됩니다. (예를 들면, item.type 으로 Schema.slack.types.user_id 사용)

다중선택을 위한 선택메뉴 요소 예제 입니다.

{
  name: "people",
  title: "People",
  type: Schema.types.array, // => multi-select
  // The type of the array item
  items: { type: Schema.slack.types.user_id }, // => "multi_users_select"
  default: ["U12345", "U23456"],
},

Full Example Form

옵션이 너무 많죠. 그래서 너무 길어지는 튜토리얼 대신, 모든 가능한 요소들을 입력 받는 폼 예제를 다음과 같이 만들어보았습니다. 참고 하시기 바랍니다.

{
  name: "recipient",
  title: "Recipient",
  type: Schema.slack.types.user_id, // => "users_select"
  default: workflow.inputs.user_id,
},
{
  name: "channel",
  title: "Channel to send message to",
  type: Schema.slack.types.channel_id, // => "channels_select"
  default: workflow.inputs.channel_id,
},
{
  name: "message",
  title: "Message to recipient",
  type: Schema.types.string, // => "plain_text_input"
  long: true, // => multiline: true
  minLength: 1, // inclusive
  maxLength: 100, // inclusive
},
{
  name: "favorite_animal",
  title: "Favorite Animal",
  type: Schema.types.string, // => "static_select"
  choices: [
    { value: "dog", title: "Dog" },
    { value: "cat", title: "Cat" },
  ],
  enum: ["dog", "cat"],
  default: "cat",
},
{
  name: "favorite_animals",
  title: "Favorite Animals",
  type: Schema.types.array, // => "mutli_static_select"
  items: {
    type: Schema.types.string,
    choices: [
      { value: "dog", title: "Dog" },
      { value: "cat", title: "Cat" },
    ],
    enum: ["dog", "cat"],
  },
  maxItems: 2,
  default: ["cat"],
},
{
  name: "contact",
  title: "Your Email Address",
  type: Schema.types.string,
  format: "email", // => "email_text_input"
},
{
  name: "channels",
  title: "Favorite Channels",
  type: Schema.types.array, // => "multi_channels_select"
  items: { type: Schema.slack.types.channel_id },
},
{
  name: "team_members",
  title: "Team Members",
  type: Schema.types.array, // => "multi_users_select"
  items: { type: Schema.slack.types.user_id },
},
{
  name: "approved",
  title: "Already Approved by My Manager",
  type: Schema.types.boolean, // => "checkboxes"
},
{
  name: "count",
  title: "Count",
  type: Schema.types.integer, // => "number_input" with is_decimal_allowed: false
},
{
  name: "amount",
  title: "Amount",
  type: Schema.types.number, // => "number_input"
},
{
  name: "due_date",
  title: "Due Date",
  type: Schema.slack.types.date, // => "datepicker"
},
{
  name: "due_time",
  title: "Due Date Time",
  type: Schema.slack.types.timestamp, // => "datepicker" + "timepicker"
},
{
  name: "rich_text",
  title: "Rich Text Input",
  type: Schema.slack.types.rich_text,
},

실행해보시면 다음과 같은 모양새가 됩니다.

Wrapping Up

이번 튜토리얼에서는 다음에 대한 내용을 배웠습니다.

  • 다양한 요소들을 가진 빌트인 폼 정의하기

이 프로젝트는 다음 링크에서 찾아보실 수 있습니다. https://github.com/seratch/slack-next-generation-platform-tutorials/tree/main/07_Built-in_Forms

이번 튜토리얼도 즐거우셨으면 좋겠네요. 마찬가지로 피드백이나 코멘트가 있으시다면 트위터 (@seratch) 로 연락주시거나, 여기 남겨주세요.

Happy hacking with Slack’s next-generation platform 🚀

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다