๐ค [Lv.2] ์ฌ๋ ์ฌ๋์ ๋ช ๋ น์ด๋ก ์์ธ ์ ์ฅ ๋ด ๋ง๋ค๊ธฐ โ
IMPORTANT
์ด ๊ฐ์ด๋์์๋ ์ฌ๋์์ /save ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ ๋ '๋ก๋ฉ ์ค' ํ๋ฉด์ ๋จผ์ ๋ณด์ฌ์ฃผ๊ณ , ๋ฐฑ์๋์์ ํ/ํด๋ ์ ๋ณด๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์จ ๋ค ์
๋ ฅ ํผ์ผ๋ก ์
๋ฐ์ดํธํ๋ UX ํจํด์ ๋ค๋ฃน๋๋ค.
โ ๏ธ ๊ฒฝ๊ณ : ๋์ ๋์ด๋
- ๋น๋๊ธฐ UI ์
๋ฐ์ดํธ: ์ฌ๋์
views.open๊ณผviews.update๋ฅผ ์ฐ๊ณํ์ฌ ํ์์์์ ๋ฐฉ์งํ๋ ๊ธฐ์ ์ด ์ฌ์ฉ๋ฉ๋๋ค. - ๋ณตํฉ ๋ฐ์ดํฐ ์ฒ๋ฆฌ: ํ ๋ฆฌ์คํธ์ ๊ฐ ํ์ ํด๋ ๋ฆฌ์คํธ๋ฅผ ์กฐํํ๊ณ ๋ณํฉ(
Merge)ํ๋ ๋ก์ง์ด ํฌํจ๋ฉ๋๋ค. - ์ฐธ๊ณ ๋ฌธ์: Slack ๊ณต์ ๋ฌธ์: Handling user interactions (3s timeout)
- ๋๊ตฌ: Slack Block Kit Builder (๋ชจ๋ฌ ๋์์ธ ํ์ ๋๊ตฌ)
1. n8n ์ํฌํ๋ก์ฐ ๊ตฌ์ฑ โ
์ ์ฒด ์ํฌํ๋ก์ฐ๋ ์ฌ๋์ '3์ด ํ์์์(3s Timeout)' ์ ํ์ ์ฐํํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์์ ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ค๊ณ๋์์ต๋๋ค.
- Phase A (์ด๊ธฐํ ๋ฐ ๋ทฐ ๋ก๋ฉ):
/save์์ ์ฆ์200 OK์๋ต (์ฐ๊ฒฐ ์ ์ง)- "๋ก๋ฉ ์ค..." ๋ชจ๋ฌ์ ์ฐ์ ํ์ (
views.open) - ๋ฐฑ์๋์์ ํ/ํด๋ ๋ชฉ๋ก์ ๋น๋๊ธฐ๋ก ์กฐํ ๋ฐ ๋ณํฉ
- ๋ก๋ฉ ๋ชจ๋ฌ์ "์ค์ ์
๋ ฅ ํผ" ์ผ๋ก ๊ต์ฒด (
views.update)
- Phase B (๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ํผ๋๋ฐฑ):
- ๋ชจ๋ฌ '์ ์ถ' ์์ ์ฆ์
200 OK์๋ต - ๋ฐ์ดํฐ ๊ฐ๊ณต ๋ฐ ๋ฐฑ์๋ ์ ์ฅ API ํธ์ถ
successํ๋ ๊ฐ์ ๋ฐ๋ผ ์ฑ๊ณต(์ ์ฒด ์๋ฆผ) / ์คํจ(๊ฐ์ธ ์๋ฆผ) ๋ถ๊ธฐ ์ฒ๋ฆฌ
2. ์์ธ ์ค์ ๋จ๊ณ โ
Step 1: ์ฌ๋ ์ฑ ์ค์ (Slash Command & Interactivity) โ
์ฌ๋ API ์ค์ ํ์ด์ง์์ n8n๊ณผ ๋ํํ ์ ์๋ ๋ ๊ฐ์ ํต๋ก๋ฅผ ์ฝ๋๋ค.
Slash Command ๋ฑ๋ก:
- Command:
/save - Request URL: Phase A์ ์์์ ์ธ
saveWebhook ๋ ธ๋ URL์ ์ ๋ ฅํฉ๋๋ค.
- Command:
Interactivity ์ค์ :
- Request URL: Phase B์ ์์์ ์ธ
InteractivityWebhook ๋ ธ๋ URL์ ์ ๋ ฅํฉ๋๋ค.
- Request URL: Phase B์ ์์์ ์ธ
Step 2: ํ์์์ ๋ฐฉ์ง๋ฅผ ์ํ '์ ์๋ต' ๋ฐ '๋ก๋ฉ ๋ทฐ' ์ ๋ต โ
์ฌ๋์ 3์ด ๋ด์ ์๋ต์ด ์์ผ๋ฉด ์๋ฌ๋ฅผ ๋ฐ์์ํต๋๋ค. ๋ฐฑ์๋ API ํต์ ์ 3์ด๋ฅผ ๋๊ธธ ์ ์์ผ๋ฏ๋ก, ์ผ๋จ ํ๋ฉด์ ๋์๋๊ณ ๋์ค์ ๋ด์ฉ์ ์ฑ์ฐ๋ ์ ๋ต์ ์ฌ์ฉํฉ๋๋ค.
Respond to Webhook Node (
์์ ์๋ต (200 ok)4):save์นํ ์ด ๋ค์ด์ค์๋ง์ ์ฐ๊ฒฐํ์ฌ ์ฌ๋์๊ฒ200 OK๋ฅผ ๋ณด๋ ๋๋ค. ์ด์ n8n์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ณ์ ์์ ํ ์ ์์ต๋๋ค.
Code Node (
์ค์ ๋ก๋ฉ ๋ทฐ ์ค๋น):- ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ค ์์ "๋ก๋ฉ ์ค..." ๋ฉ์์ง๊ฐ ๋ด๊ธด ๋ธ๋ก(Block)์ ์ ์ํฉ๋๋ค.
HTTP Request Node (
์ค์ ๋ก๋ฉ ๋ทฐ ๋์ฐ๊ธฐ):- ์ฌ๋ API
views.open์ ํธ์ถํ์ฌ ๋ก๋ฉ ๋ชจ๋ฌ์ ๋์๋๋ค. ์ด๋ ๋ฐํ๋๋view_id๋ ๋์ค์ ํ๋ฉด์ ๊ต์ฒดํ ๋ ํ์์ ์ด๋ฏ๋ก ์ ์ฑ๊ฒจ๋ก๋๋ค.
- ์ฌ๋ API
Step 3: ๋ฐ์ดํฐ ๋ณํฉ ๋ฃจํ ๋ฐ ๋ทฐ ์ ๋ฐ์ดํธ (ํต์ฌ ๋ก์ง) โ
์ฌ์ฉ์๊ฐ ์ ํํ ํ๊ณผ ํด๋ ๋ชฉ๋ก์ ๋ง๋ค๊ธฐ ์ํด ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๊ณ ๊ตฌ์กฐํํฉ๋๋ค.
HTTP Request Node (
๋ฐฑ์๋ ํ ์กฐํ API): ์ ์ฒด ํ ๋ฆฌ์คํธ๋ฅผ ๊ฐ์ ธ์ต๋๋ค.Loop Logic (
ํ ๋ฆฌ์คํธ ๋ถํ->ํ ๋ฐ๋ณต):- ๊ฐ์ ธ์จ ํ ๊ฐ์๋งํผ ๋ฐ๋ณต๋ฌธ์ ์คํํฉ๋๋ค.
- HTTP Request (
๋ฐฑ์๋ ํด๋ ์กฐํ API): ํ์ฌ ์์์teamUuid๋ฅผ ์ฌ์ฉํด ํด๋น ํ์ ํด๋ ๋ชฉ๋ก์ ๊ฐ์ ธ์ต๋๋ค. - Set Node (
ํ ํด๋ ๋งคํ): ํ ์ ๋ณด์ ํด๋ ์ ๋ณด๋ฅผ ํ๋์ ๊ฐ์ฒด({ team: ..., folders: ... })๋ก ํฉ์นฉ๋๋ค.
Code Node (
์ ์ฅ ๋ทฐ ๊ตฌ์ฑ):- ๋ฐ๋ณต๋ฌธ์ด ๋๋๊ณ ๋ชจ์ธ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํด ์ฌ๋์ Static Select(์ต์ ๊ทธ๋ฃน) ํํ์ JSON์ ์์ฑํฉ๋๋ค.
- ์ด์ ๋จ๊ณ์์ ๋ฐ์
view_id๋ฅผ ์ฐธ์กฐํ์ฌ ์ ๋ฐ์ดํธ ๋์์ ์ง์ ํฉ๋๋ค.
HTTP Request Node (
์ ์ฅ ๋ทฐ ๋์ฐ๊ธฐ):- ์ฌ๋ API
views.update๋ฅผ ํธ์ถํฉ๋๋ค. ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์๋ "๋ก๋ฉ ์ค" ํ๋ฉด์ "ํ/ํด๋ ์ ํ ํผ" ์ผ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค.
- ์ฌ๋ API
Step 4: ์ ์ถ ๋ฐ์ดํฐ ๊ฐ๊ณต ๋ฐ API ์ ์ฅ ์์ฒญ โ
์ฌ์ฉ์๊ฐ ํผ์ ๋ค ์ฑ์ฐ๊ณ '์ ์ฅ'์ ๋๋ ์ ๋์ ์ฒ๋ฆฌ ๊ณผ์ ์ ๋๋ค.
Webhook & Respond Node (
Interactivity->์์ ์๋ต):- ๋ฒํผ ํด๋ฆญ๋ ์ธํฐ๋์
์ด๋ฏ๋ก, ๋ค์ ํ๋ฒ ์ฆ์
200 OK๋ฅผ ๋ณด๋ด ์๋ฌ ์ฐฝ์ ๋ง์ต๋๋ค.
- ๋ฒํผ ํด๋ฆญ๋ ์ธํฐ๋์
์ด๋ฏ๋ก, ๋ค์ ํ๋ฒ ์ฆ์
Code Node (
์ ์ฅ ๋ฐ์ดํฐ ๊ฐ๊ณต):- ์ฌ๋ Payload(
view.state.values)๋ฅผ ํ์ฑํ์ฌtitle,url,summary,tags๋ฑ์ ์ถ์ถํฉ๋๋ค. private_metadata์ ์จ๊ฒจ๋ ์๋ณธurl๊ณผchannelId๋ฅผ ๋ณต์ํฉ๋๋ค.
- ์ฌ๋ Payload(
HTTP Request Node (
๋ฐฑ์๋ ๋งํฌ ์ถ๊ฐ API):- ์ ์ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐฑ์๋ ์๋ฒ(
POST /links)๋ก ์ ์กํฉ๋๋ค. ์ด๋onError: continue์ค์ ์ ํตํด ์๋ฌ๊ฐ ๋๋ ์ํฌํ๋ก์ฐ๊ฐ ๋ฉ์ถ์ง ์๊ฒ ํฉ๋๋ค.
- ์ ์ ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฐฑ์๋ ์๋ฒ(
Step 5: ์ฑ๊ณต ์ฌ๋ถ์ ๋ฐ๋ฅธ๋ถ๊ธฐ ์ฒ๋ฆฌ โ
API ํธ์ถ์ด ๋๋ฌ๋ค๊ณ ์ฑ๊ณตํ ๊ฒ์ด ์๋๋๋ค. ๋ฐฑ์๋๊ฐ ๋ณด๋ด์ค ์๋ต์ ๊ฒ์ฆํด์ผ ํฉ๋๋ค.
If Node (
๋งํฌ ์ถ๊ฐ ์ฑ๊ณต ?):- API ์๋ต JSON ๋ด์
successํ๋๊ฐtrue์ธ์ง ํ์ธํฉ๋๋ค.
- API ์๋ต JSON ๋ด์
True (์ฑ๊ณต ์):
- Code Node (
์ฑ๊ณต ๋ฉ์ธ์ง ์ค๋น):โ <@user>๋์ด ๋งํฌ๋ฅผ ์ ์ฅํ์ต๋๋ค!ํํ์ ๋ฉ์์ง๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. - HTTP Request (
๋งํฌ ์ ์ฅ ์๋ฃ ๋ฉ์ธ์ง ์ ์ก):chat.postMessage๋ก ์ฑ๋์ ๊ณต๊ฐ ์๋ฆผ์ ๋ณด๋ ๋๋ค.
- Code Node (
False (์คํจ ์):
- Code Node (
์คํจ ๋ฉ์ธ์ง ์ค๋น): ๋ฐฑ์๋์์ ๋ฐํํmessage(์๋ฌ ์ฌ์ )๋ฅผ ํฌํจํ์ฌ ๊ฒฝ๊ณ ๋ฉ์์ง๋ฅผ ๊ตฌ์ฑํฉ๋๋ค. - HTTP Request (
๋งํฌ ์ ์ฅ ์คํจ ๋ฉ์ธ์ง ์ ์ก):chat.postEphemeralAPI๋ฅผ ์ฌ์ฉํ์ฌ ํด๋น ์ ์ ์๊ฒ๋ง ๋ณด์ด๋ ์๋ฌ ๋ฉ์์ง๋ฅผ ๋ณด๋ ๋๋ค.
- Code Node (
3. ํต์ฌ ๊ธฐ์ ํฌ์ธํธ (Tip) โ
- View ID์ ์๋ช
์ฃผ๊ธฐ:
views.open์ผ๋ก ์์ฑ๋view_id๋ ๋ชจ๋ฌ์ ์๋ณํ๋ ์ ์ผํ ํค์ ๋๋ค.views.update๋ฅผ ํ ๋ ์ด ID๊ฐ ํ๋ฆฌ๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๋ฏ๋ก ๋ฐ์ดํฐ ํ๋ฆ์ ์ฃผ์ํ์ธ์. - Private Metadata ํ์ฉ: ๋ชจ๋ฌ์ ์ฌ๋ฌ ๋จ๊ณ๋ก ์
๋ฐ์ดํธ๋์ง๋ง, ์ฒ์์
/save๋ช ๋ น์ด๋ฅผ ์ณค๋ ์ฑ๋ ID๋ ์๋ณธ ํ ์คํธ๋ ๊ณ์ ์ ์ง๋์ด์ผ ํฉ๋๋ค. ์ด๋ฅผprivate_metadataํ๋์ JSON ๋ฌธ์์ด๋ก ๋ฃ์ด ๊ณ์ ๋๊ฒจ์ฃผ๋ ๊ฒ์ด ํต์ฌ์ ๋๋ค. - ์ฌ์ฉ์ ๊ฒฝํ(UX): ๋ฐ์ดํฐ ๋ก๋ฉ์ด 1์ด๋ผ๋ ๊ฑธ๋ฆฐ๋ค๋ฉด '๋ก๋ฉ ๋ทฐ'๋ฅผ ๋จผ์ ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ฌ์ฉ์๋ "๋ฒํผ์ ๋๋ ๋๋ฐ ์ ๋ฐ์์ด ์์ง?"๋ผ๊ณ ์๊ฐํ๊ฒ ๋ฉ๋๋ค.
4. ๊ฒฐ๊ณผ ์์ โ
- ์ฑ๊ณต ์: ์ฑ๋์ ์ ์ฅ๋ ๋งํฌ ์ ๋ณด๊ฐ ๊ณต์ ๋ฉ๋๋ค.
- ์คํจ ์: "์ฃ์กํฉ๋๋ค, ๋งํฌ ์ ์ฅ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. (์ฌ์ : ์ค๋ณต๋ URL)"๊ณผ ๊ฐ์ ๋ฉ์์ง๊ฐ ๋์๊ฒ๋ง ํ์๋ฉ๋๋ค.
๐ ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ฐธ๊ณ โ
์ฌ์ฉํ ์ ์๋ API์ ๋ํ ์ ๋ณด๋ ์๋ ๋ ํผ๋ฐ์ค๋ฅผ ํ์ธํ์ธ์.
๐ API Reference
๊ตฌ์ฑ๋ n8n ์ํฌํ๋ก์ฐ ํ์ผ โ
- Exam_SaveBot_n8n.json
- ์ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ์ํฌํ๋ก์ฐ JSON ํ์ผ์ ๋ค์ด๋ก๋ํ ํ, n8n์์ ๊ฐ์ ธ์ค๊ธฐ(import)ํ์ฌ ์ฌ์ฉํ์ธ์.