Skip to content

๐Ÿค– [Lv.2] ์Šฌ๋ž™ ์Šฌ๋ž˜์‹œ ๋ช…๋ น์–ด๋กœ ์ƒ์„ธ ์ €์žฅ ๋ด‡ ๋งŒ๋“ค๊ธฐ โ€‹

IMPORTANT

์ด ๊ฐ€์ด๋“œ์—์„œ๋Š” ์Šฌ๋ž™์—์„œ /save ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ–ˆ์„ ๋•Œ '๋กœ๋”ฉ ์ค‘' ํ™”๋ฉด์„ ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ , ๋ฐฑ์—”๋“œ์—์„œ ํŒ€/ํด๋” ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜จ ๋’ค ์ž…๋ ฅ ํผ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋Š” UX ํŒจํ„ด์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

โš ๏ธ ๊ฒฝ๊ณ : ๋†’์€ ๋‚œ์ด๋„

  • ๋น„๋™๊ธฐ UI ์—…๋ฐ์ดํŠธ: ์Šฌ๋ž™์˜ views.open๊ณผ views.update๋ฅผ ์—ฐ๊ณ„ํ•˜์—ฌ ํƒ€์ž„์•„์›ƒ์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ธฐ์ˆ ์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ๋ณตํ•ฉ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ: ํŒ€ ๋ฆฌ์ŠคํŠธ์™€ ๊ฐ ํŒ€์˜ ํด๋” ๋ฆฌ์ŠคํŠธ๋ฅผ ์กฐํšŒํ•˜๊ณ  ๋ณ‘ํ•ฉ(Merge)ํ•˜๋Š” ๋กœ์ง์ด ํฌํ•จ๋ฉ๋‹ˆ๋‹ค.
  • ์ฐธ๊ณ  ๋ฌธ์„œ: Slack ๊ณต์‹ ๋ฌธ์„œ: Handling user interactions (3s timeout)
  • ๋„๊ตฌ: Slack Block Kit Builder (๋ชจ๋‹ฌ ๋””์ž์ธ ํ•„์ˆ˜ ๋„๊ตฌ)

1. n8n ์›Œํฌํ”Œ๋กœ์šฐ ๊ตฌ์„ฑ โ€‹

image

์ „์ฒด ์›Œํฌํ”Œ๋กœ์šฐ๋Š” ์Šฌ๋ž™์˜ '3์ดˆ ํƒ€์ž„์•„์›ƒ(3s Timeout)' ์ œํ•œ์„ ์šฐํšŒํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

  • Phase A (์ดˆ๊ธฐํ™” ๋ฐ ๋ทฐ ๋กœ๋”ฉ):
  1. /save ์ˆ˜์‹  ์ฆ‰์‹œ 200 OK ์‘๋‹ต (์—ฐ๊ฒฐ ์œ ์ง€)
  2. "๋กœ๋”ฉ ์ค‘..." ๋ชจ๋‹ฌ์„ ์šฐ์„  ํ‘œ์‹œ (views.open)
  3. ๋ฐฑ์—”๋“œ์—์„œ ํŒ€/ํด๋” ๋ชฉ๋ก์„ ๋น„๋™๊ธฐ๋กœ ์กฐํšŒ ๋ฐ ๋ณ‘ํ•ฉ
  4. ๋กœ๋”ฉ ๋ชจ๋‹ฌ์„ "์‹ค์ œ ์ž…๋ ฅ ํผ" ์œผ๋กœ ๊ต์ฒด (views.update)
  • Phase B (๋ฐ์ดํ„ฐ ์ €์žฅ ๋ฐ ํ”ผ๋“œ๋ฐฑ):
  1. ๋ชจ๋‹ฌ '์ œ์ถœ' ์ˆ˜์‹  ์ฆ‰์‹œ 200 OK ์‘๋‹ต
  2. ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต ๋ฐ ๋ฐฑ์—”๋“œ ์ €์žฅ API ํ˜ธ์ถœ
  3. success ํ•„๋“œ ๊ฐ’์— ๋”ฐ๋ผ ์„ฑ๊ณต(์ „์ฒด ์•Œ๋ฆผ) / ์‹คํŒจ(๊ฐœ์ธ ์•Œ๋ฆผ) ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ

2. ์ƒ์„ธ ์„ค์ • ๋‹จ๊ณ„ โ€‹

Step 1: ์Šฌ๋ž™ ์•ฑ ์„ค์ • (Slash Command & Interactivity) โ€‹

qq1

์Šฌ๋ž™ API ์„ค์ • ํŽ˜์ด์ง€์—์„œ n8n๊ณผ ๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐœ์˜ ํ†ต๋กœ๋ฅผ ์—ฝ๋‹ˆ๋‹ค.

  1. Slash Command ๋“ฑ๋ก:

    • Command: /save
    • Request URL: Phase A์˜ ์‹œ์ž‘์ ์ธ save Webhook ๋…ธ๋“œ URL์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.
  2. Interactivity ์„ค์ •:

    • Request URL: Phase B์˜ ์‹œ์ž‘์ ์ธ Interactivity Webhook ๋…ธ๋“œ URL์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

Step 2: ํƒ€์ž„์•„์›ƒ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ '์„ ์‘๋‹ต' ๋ฐ '๋กœ๋”ฉ ๋ทฐ' ์ „๋žต โ€‹

image

์Šฌ๋ž™์€ 3์ดˆ ๋‚ด์— ์‘๋‹ต์ด ์—†์œผ๋ฉด ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ API ํ†ต์‹ ์€ 3์ดˆ๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์ผ๋‹จ ํ™”๋ฉด์„ ๋„์›Œ๋‘๊ณ  ๋‚˜์ค‘์— ๋‚ด์šฉ์„ ์ฑ„์šฐ๋Š” ์ „๋žต์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  1. Respond to Webhook Node (์ˆ˜์‹  ์‘๋‹ต (200 ok)4):

    • save ์›นํ›…์ด ๋“ค์–ด์˜ค์ž๋งˆ์ž ์—ฐ๊ฒฐํ•˜์—ฌ ์Šฌ๋ž™์—๊ฒŒ 200 OK๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด์ œ n8n์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ณ„์† ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. Code Node (์„ค์ • ๋กœ๋”ฉ ๋ทฐ ์ค€๋น„):

    • ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ค„ ์ž„์‹œ "๋กœ๋”ฉ ์ค‘..." ๋ฉ”์‹œ์ง€๊ฐ€ ๋‹ด๊ธด ๋ธ”๋ก(Block)์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  3. HTTP Request Node (์„ค์ • ๋กœ๋”ฉ ๋ทฐ ๋„์šฐ๊ธฐ):

    • ์Šฌ๋ž™ API views.open์„ ํ˜ธ์ถœํ•˜์—ฌ ๋กœ๋”ฉ ๋ชจ๋‹ฌ์„ ๋„์›๋‹ˆ๋‹ค. ์ด๋•Œ ๋ฐ˜ํ™˜๋˜๋Š” view_id ๋Š” ๋‚˜์ค‘์— ํ™”๋ฉด์„ ๊ต์ฒดํ•  ๋•Œ ํ•„์ˆ˜์ ์ด๋ฏ€๋กœ ์ž˜ ์ฑ™๊ฒจ๋‘ก๋‹ˆ๋‹ค.

Step 3: ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ ๋ฃจํ”„ ๋ฐ ๋ทฐ ์—…๋ฐ์ดํŠธ (ํ•ต์‹ฌ ๋กœ์ง) โ€‹

image

์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•  ํŒ€๊ณผ ํด๋” ๋ชฉ๋ก์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๊ณ  ๊ตฌ์กฐํ™”ํ•ฉ๋‹ˆ๋‹ค.

  1. HTTP Request Node (๋ฐฑ์—”๋“œ ํŒ€ ์กฐํšŒ API): ์ „์ฒด ํŒ€ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.

  2. Loop Logic (ํŒ€ ๋ฆฌ์ŠคํŠธ ๋ถ„ํ•  -> ํŒ€ ๋ฐ˜๋ณต):

    • ๊ฐ€์ ธ์˜จ ํŒ€ ๊ฐœ์ˆ˜๋งŒํผ ๋ฐ˜๋ณต๋ฌธ์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • HTTP Request (๋ฐฑ์—”๋“œ ํด๋” ์กฐํšŒ API): ํ˜„์žฌ ์ˆœ์„œ์˜ teamUuid๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด๋‹น ํŒ€์˜ ํด๋” ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
    • Set Node (ํŒ€ ํด๋” ๋งคํ•‘): ํŒ€ ์ •๋ณด์™€ ํด๋” ์ •๋ณด๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด({ team: ..., folders: ... })๋กœ ํ•ฉ์นฉ๋‹ˆ๋‹ค.
  3. Code Node (์ €์žฅ ๋ทฐ ๊ตฌ์„ฑ):

    • ๋ฐ˜๋ณต๋ฌธ์ด ๋๋‚˜๊ณ  ๋ชจ์ธ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด ์Šฌ๋ž™์˜ Static Select(์˜ต์…˜ ๊ทธ๋ฃน) ํ˜•ํƒœ์˜ JSON์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • ์ด์ „ ๋‹จ๊ณ„์—์„œ ๋ฐ›์€ view_id๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์—…๋ฐ์ดํŠธ ๋Œ€์ƒ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  4. HTTP Request Node (์ €์žฅ ๋ทฐ ๋„์šฐ๊ธฐ):

    • ์Šฌ๋ž™ API views.update๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด๊ณ  ์žˆ๋˜ "๋กœ๋”ฉ ์ค‘" ํ™”๋ฉด์„ "ํŒ€/ํด๋” ์„ ํƒ ํผ" ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.

Step 4: ์ œ์ถœ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต ๋ฐ API ์ €์žฅ ์š”์ฒญ โ€‹

image

์‚ฌ์šฉ์ž๊ฐ€ ํผ์„ ๋‹ค ์ฑ„์šฐ๊ณ  '์ €์žฅ'์„ ๋ˆŒ๋ €์„ ๋•Œ์˜ ์ฒ˜๋ฆฌ ๊ณผ์ •์ž…๋‹ˆ๋‹ค.

  1. Webhook & Respond Node (Interactivity -> ์ˆ˜์‹  ์‘๋‹ต):

    • ๋ฒ„ํŠผ ํด๋ฆญ๋„ ์ธํ„ฐ๋ž™์…˜์ด๋ฏ€๋กœ, ๋‹ค์‹œ ํ•œ๋ฒˆ ์ฆ‰์‹œ 200 OK๋ฅผ ๋ณด๋‚ด ์—๋Ÿฌ ์ฐฝ์„ ๋ง‰์Šต๋‹ˆ๋‹ค.
  2. Code Node (์ €์žฅ ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต):

    • ์Šฌ๋ž™ Payload(view.state.values)๋ฅผ ํŒŒ์‹ฑํ•˜์—ฌ title, url, summary, tags ๋“ฑ์„ ์ถ”์ถœํ•ฉ๋‹ˆ๋‹ค.
    • private_metadata์— ์ˆจ๊ฒจ๋‘” ์›๋ณธ url๊ณผ channelId๋ฅผ ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.
  3. HTTP Request Node (๋ฐฑ์—”๋“œ ๋งํฌ ์ถ”๊ฐ€ API):

    • ์ •์ œ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„(POST /links)๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ onError: continue ์„ค์ •์„ ํ†ตํ•ด ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ์›Œํฌํ”Œ๋กœ์šฐ๊ฐ€ ๋ฉˆ์ถ”์ง€ ์•Š๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

Step 5: ์„ฑ๊ณต ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ โ€‹

API ํ˜ธ์ถœ์ด ๋๋‚ฌ๋‹ค๊ณ  ์„ฑ๊ณตํ•œ ๊ฒƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋ฐฑ์—”๋“œ๊ฐ€ ๋ณด๋‚ด์ค€ ์‘๋‹ต์„ ๊ฒ€์ฆํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  1. If Node (๋งํฌ ์ถ”๊ฐ€ ์„ฑ๊ณต ?):

    • API ์‘๋‹ต JSON ๋‚ด์˜ success ํ•„๋“œ๊ฐ€ true์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  2. True (์„ฑ๊ณต ์‹œ):

    • Code Node (์„ฑ๊ณต ๋ฉ”์„ธ์ง€ ์ค€๋น„): โœ… <@user>๋‹˜์ด ๋งํฌ๋ฅผ ์ €์žฅํ–ˆ์Šต๋‹ˆ๋‹ค! ํ˜•ํƒœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • HTTP Request (๋งํฌ ์ €์žฅ ์™„๋ฃŒ ๋ฉ”์„ธ์ง€ ์ „์†ก): chat.postMessage๋กœ ์ฑ„๋„์— ๊ณต๊ฐœ ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
  3. False (์‹คํŒจ ์‹œ):

    • Code Node (์‹คํŒจ ๋ฉ”์„ธ์ง€ ์ค€๋น„): ๋ฐฑ์—”๋“œ์—์„œ ๋ฐ˜ํ™˜ํ•œ message (์—๋Ÿฌ ์‚ฌ์œ )๋ฅผ ํฌํ•จํ•˜์—ฌ ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    • HTTP Request (๋งํฌ ์ €์žฅ ์‹คํŒจ ๋ฉ”์„ธ์ง€ ์ „์†ก): chat.postEphemeral API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์œ ์ €์—๊ฒŒ๋งŒ ๋ณด์ด๋Š” ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

3. ํ•ต์‹ฌ ๊ธฐ์ˆ  ํฌ์ธํŠธ (Tip) โ€‹

  • View ID์˜ ์ƒ๋ช… ์ฃผ๊ธฐ: views.open์œผ๋กœ ์ƒ์„ฑ๋œ view_id๋Š” ๋ชจ๋‹ฌ์„ ์‹๋ณ„ํ•˜๋Š” ์œ ์ผํ•œ ํ‚ค์ž…๋‹ˆ๋‹ค. views.update๋ฅผ ํ•  ๋•Œ ์ด ID๊ฐ€ ํ‹€๋ฆฌ๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ํ๋ฆ„์— ์ฃผ์˜ํ•˜์„ธ์š”.
  • Private Metadata ํ™œ์šฉ: ๋ชจ๋‹ฌ์€ ์—ฌ๋Ÿฌ ๋‹จ๊ณ„๋กœ ์—…๋ฐ์ดํŠธ๋˜์ง€๋งŒ, ์ฒ˜์Œ์— /save ๋ช…๋ น์–ด๋ฅผ ์ณค๋˜ ์ฑ„๋„ ID๋‚˜ ์›๋ณธ ํ…์ŠคํŠธ๋Š” ๊ณ„์† ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ private_metadata ํ•„๋“œ์— JSON ๋ฌธ์ž์—ด๋กœ ๋„ฃ์–ด ๊ณ„์† ๋„˜๊ฒจ์ฃผ๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX): ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์ด 1์ดˆ๋ผ๋„ ๊ฑธ๋ฆฐ๋‹ค๋ฉด '๋กœ๋”ฉ ๋ทฐ'๋ฅผ ๋จผ์ € ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์‚ฌ์šฉ์ž๋Š” "๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋Š”๋ฐ ์™œ ๋ฐ˜์‘์ด ์—†์ง€?"๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

4. ๊ฒฐ๊ณผ ์˜ˆ์‹œ โ€‹

image
  • ์„ฑ๊ณต ์‹œ: ์ฑ„๋„์— ์ €์žฅ๋œ ๋งํฌ ์ •๋ณด๊ฐ€ ๊ณต์œ ๋ฉ๋‹ˆ๋‹ค.
image
  • ์‹คํŒจ ์‹œ: "์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค, ๋งํฌ ์ €์žฅ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. (์‚ฌ์œ : ์ค‘๋ณต๋œ URL)"๊ณผ ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‚˜์—๊ฒŒ๋งŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“š ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ฐธ๊ณ  โ€‹

์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” API์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ์•„๋ž˜ ๋ ˆํผ๋Ÿฐ์Šค๋ฅผ ํ™•์ธํ•˜์„ธ์š”.

๐Ÿ‘‰ API Reference

๊ตฌ์„ฑ๋œ n8n ์›Œํฌํ”Œ๋กœ์šฐ ํŒŒ์ผ โ€‹

  • Exam_SaveBot_n8n.json
  • ์œ„ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์—ฌ ์›Œํฌํ”Œ๋กœ์šฐ JSON ํŒŒ์ผ์„ ๋‹ค์šด๋กœ๋“œํ•œ ํ›„, n8n์—์„œ ๊ฐ€์ ธ์˜ค๊ธฐ(import)ํ•˜์—ฌ ์‚ฌ์šฉํ•˜์„ธ์š”.