ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Slack] SpringBoot Interactivity Message 전송
    기타 2022. 4. 9. 17:26
    반응형

    이 글은 혼자 학습한 내용을 바탕으로 작성되었습니다.

    틀리거나 잘못된 정보가 있을 수 있습니다.

    댓글로 알려주시면 수정하도록 하겠습니다.


     

    1. Interactivity Message란

    Interactivity Message

    Interactivity Message란 상호 작용이 가능한 Message입니다. 즉 Slack의 메시지에서 Input, TextArea, Button과 같이 메시지를 통해 상호작용이 가능한 Message입니다.

     

    이번 포스팅에서는 이 Interactivity Message를 SpringBoot에서 전송하고 해당 Message에서 입력 값들을 전달받아 활용해보고자 합니다.

     

    2.Webhook URL

    이전 포스팅을 통해 Slack의 Webhook URL을 받았습니다. Interactivity Message 또한 이 Webhook URL을 통해 Message를 전달하고 사용자가 원하는 URL로 Message의 입력값 등 여러 데이터를 받아보고자 합니다.

     

    Webhook과 관련된 정보는 이전 글에 작성되어 있으므로 모르시는 분들께서는 이전 글을 먼저 읽어보시는 것을 추천드립니다.

     

    3. Interactivity Message 설정

    좌측 메뉴에서 Interativity & Shortcuts 메뉴로 이동을 합니다.

    이후 화면의 우측 상단의 Toggle 버튼을 클릭하여 On으로 활성화를 진행합니다.

    활성화가 되면 하단에 Request URL이 생성됩니다. 여기에 Message에 대한 결과를 돌려받고자 하는 URL을 입력하면 됩니다.

     

    예를 들어 'http://example.com/slack/request'와 같이 작성하면 됩니다.

     

    이것으로 Slack에서의 Interactivity Message를 사용할 준비는 완료가 되었습니다.

     

    4. SpringBoot에서 Interactivity Message 전송

    이전 글에서 Webhook을 통해 Message를 전달할 때 Json 포맷을 이용하여 Text 값을 전달하면 해당 값의 Text가 Message로 전달되었습니다.

     

    Interactivity Message도 동일합니다. Interactivity Message를 구성하는 정보들을 Json 포맷으로 전달만 하면 Slack에서는 해당 포맷을 파싱 하여 Message로 만들어 표시해주는 것입니다.

     

    Slack에서는 이러한 Element를 Block이라고 부르며 Block들을 모아 하나의 Message를 구성하게 됩니다.

     

    Block에 대한 여러 설명은 Slack Block api 이곳에 더 자세히 설명되어 있습니다.

     

    또한 Slack Block Builder에 접속하시면 직접 Interactivity Message를 구성해 볼 수 있습니다.

     

    SpringBoot에서 Slack api 사용을 위해 의존성을 추가하여야 합니다.

    implementation 'com.google.code.gson:gson:2.8.6'
    
    implementation 'com.squareup.okhttp3:okhttp:4.2.0'
    
    implementation 'com.slack.api:slack-app-backend:1.6.2'
    implementation 'com.slack.api:slack-api-model:1.6.2'
    implementation 'com.slack.api:slack-api-client:1.6.2'

     

    위 5개의 의존성을 추가합니다.

     

    다음 Slack에 전송하고자 하는 Message를 구성하는 Block들을 Array에 담아 Webhook으로 전달합니다.

     

    Message를 구성할 Block들을 Blocks의 asBlocks메소드로 Array로 반환받습니다.

    asBlocks 메소드

    Blocks.asBlocks(
    	slackUtils.getHeader("Interavitity Message"),
    	slackUtils.getSection("Slack Message 테스트"),
    	Blocks.divider(),
    	slackUtils.getTextInput("제목", "block_title"),
    	slackUtils.getTextArea("내용", "block_content"),
    	Blocks.actions(slackUtils.getConfirmButtonBlocks())
    );

     

    이제 SlackUtils에서 각각의 Block을 만드는 메소드를 살펴보겠습니다.

     

    Block은 Builer와 Lambda를 이용하여 생성할 수도 있습니다.

     

    Header Block을 생성 메소드입니다.

    public HeaderBlock getHeader(String text) {
    	return Blocks.header(h ->
    		h.text(plainText(pt ->
    			pt.emoji(true).text(text)
    		))
    	);
    }
    public HeaderBlock getHeader(String text) {        
    	return HeaderBlock.builder()
    			.text(PlainTextObject.builder()
    					.text(text)
    					.emoji(true)
    					.build())
    			.build();
    }

     

    Section Block을 생성 메소드입니다.

    Section은 HTML의 P 태그와 비슷합니다.

    public SectionBlock getSection(String message) {
    	return Blocks.section(s -> 
    		s.text(BlockCompositions.markdownText(message))
    	);
    }
    public SectionBlock getSection(String message) {
    	return SectionBlock.builder()
    			.text(MarkdownTextObject.builder()
    					.text(message)
    					.build())
    			.build();
    }

     

    TextArea Block을 생성 메소드입니다.

    Input Block의 경우 CallBack 된 Message에서 값을 찾아오기 위해 BlockId를 지정해 주어야 합니다.

    만약 지정해주지 않으면 Random으로 배정되기 때문에 값을 찾는 과정이 복잡해집니다.

    public InputBlock getTextArea(String labelText, String actionId){
    	InputBlock inputBlock = Blocks.input(i -> 
    		i.element(BlockElements.plainTextInput(pti ->
    			pti.actionId(actionId).multiline(true))
    		).label(plainText(labelText)));
    
    	inputBlock.setBlockId(actionId);
    
    	return inputBlock;
    }
    public InputBlock getTextArea(String labelText, String actionId){
    	return InputBlock.builder()
    			.element(PlainTextInputElement.builder()
    					.actionId(actionId)
    					.multiline(true)
    					.build())
    			.label(PlainTextObject.builder()
    					.text(labelText)
    					.build())
    			.blockId(actionId)
    			.build();
    }

     

    Input Block을 생성 메소드입니다.

    public InputBlock getTextInput(String labelText, String actionId){
    	InputBlock inputBlock = Blocks.input(i ->
        i.element(BlockElements.plainTextInput(pti ->
        	pti.actionId(actionId)
    	)).label(plainText(labelText)));
    
    	inputBlock.setBlockId(actionId);
    
    	return inputBlock;
    }
    public InputBlock getTextInput(String labelText, String actionId){
    	return InputBlock.builder()
    		.element(PlainTextInputElement.builder()
    			.actionId(actionId)
    			.build())
    		.label(PlainTextObject.builder()
    			.text(labelText)
    			.build())
    		.blockId(actionId)
    		.build();
    }

     

    ActionButton Block 생성 메소드 입니다.

    먼저 Button Group을 생성하기 위해 Array로 Button들을 생성

    public List<BlockElement> getConfirmButtonBlocks() {
    	List<BlockElement> actions = new ArrayList<>();
    	actions.add(getActionButton("전송", "submit", "primary", "callback_submit"));
    	return actions;
    }
    public BlockElement getActionButton(String plainText, String value, String style, String actionId) {
    	return BlockElements.button(b ->
        	b.text(plainText(plainText, true))
    			.value(value)
    			.style(style)
    			.actionId(actionId));
    }
    public BlockElement getActionButton(String plainText, String value, String style, String actionId) {
    	return ButtonElement.builder()
    		.text(PlainTextObject.builder()
    			.text(plainText)
    			.emoji(true)
    			.build())
    		.value(value)
    		.style(style)
    		.actionId(actionId)
    		.build();
    }

     

    위 과정으로 만들어진 Block Array를 전송

    Slack.getInstance().send("Slack Webhook URL", WebhookPayloads.payload(p -> p.blocks(<만든 Block Array>)));
    Slack.getInstance().send("Slack Webhook URL", Payload.builder().blocks(<만든 Block Array>).build());

     

    위 send 메소드를 통해 Message를 전송한 결과 Slack에는 Block으로 만든 Message가 도착하게 됩니다.

     

    5. Slack Message Callback

    이제 위 Message에서 입력항목에 입력 후 전송 버튼을 누른 경우 해당 값들을 App에서 받아보는 것을 설명하도록 하겠습니다.

     

    Interactivity & Shortcut에서 설정한 Request URL을 통해 Interactivity Message 결과가 전달 됩니다.

     

    결과는 POST메소드로 JSON이 전달되므로 Controller에서는 @RequestParam으로 JSON String 값을 받아 옵니다.

    @PostMapping("/slack/callback")
    public ResponseEntity<Boolean> slackCallBack(@RequestParam String payload) {
    	notificationService.sendResponseMessage(payload);
    	return ResponseEntity.ok(true);
    }

     

    JSON String을 Slack의 BlockActionPayload 객체로 Mapping 합니다.

    public BlockActionPayload getPayload(String payloadJson){
    	return GsonFactory.createSnakeCase().fromJson(payloadJson, BlockActionPayload.class);
    }

     

    BlockActionPayload객체에 Input, TextArea 값들은 ViewState 객체의 values 속성에 담겨있습니다. 그러므로 해당 속성에 접근하여 가져와야 합니다.

     

    BlockActionPayload Class
    ViewState Class

     

    결과가 담겨있는 Map<String, Map<String, Value>>에 결과 값들이 들어있습니다.

     

    첫번째 Map은 BlockId를 Key값으하는 Block이 있습니다.

     

    두번째 Map은 Block의 Element의 ActionId를 Key값으로 하는 Element가 있습니다.

     

    Test Message로 사용할 Block들을 생성합니다.

    Block 생성
    전송 Slack Message

     

    Debug모드로 확인한 Payload values 입니다.

     

    결과 처럼 첫번째 Map은 block_input1, block_input2 를 Key값으로 가지고 있습니다.

     

    각각의 Map의 Element인 두번째 Map은 input, textarea를 Key값으로 가지고 있습니다.

     

    두번째 Map의 value에는 ViewState 객체가 있으며 이 객체의 value값이 바로 Message에서 입력한 값이 들어있는 것을 확인할 수 있습니다.

     

    그러므로 Slack의 Callback에서 입력된 값을 가져오려면 Payload 객체에서 ViewState의 values 속성값에 있는 Map에서 BlcokId와 ActionId를 통해 해당 Element에 접근하여 value를 가져올 수 있습니다.

    반응형

    댓글

Designed by Tistory.