Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/slack/block_kit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ module Layout; end

module_function

def configuration
@configuration ||= Configuration.new

yield(@configuration) if block_given?

@configuration
end
class << self
alias config configuration
end

def blocks
blocks = Blocks.new

Expand Down
15 changes: 15 additions & 0 deletions lib/slack/block_kit/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Slack
module BlockKit
class Configuration
attr_accessor :autofix_invalid_blocks

def initialize
@autofix_invalid_blocks = false
end

def autofix_invalid_blocks?
!!@autofix_invalid_blocks
end
end
end
end
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#actions
class Actions
prepend Limiters::BlockId
TYPE = 'actions'

attr_accessor :elements
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#context
class Context
prepend Limiters::BlockId
TYPE = 'context'

attr_accessor :elements
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/divider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#divider
class Divider
prepend Limiters::BlockId
TYPE = 'divider'

def initialize(block_id: nil)
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Layout
#
# https://api.slack.com/reference/block-kit/blocks#header
class Header
prepend Limiters::BlockId
TYPE = 'header'

def initialize(text:, block_id: nil, emoji: nil)
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/image.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#context
class Image
prepend Limiters::BlockId
TYPE = 'image'

def initialize(url:, alt_text:, title: nil, block_id: nil, emoji: nil)
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Layout
#
# https://api.slack.com/reference/block-kit/blocks#input
class Input # rubocop:disable Metrics/ClassLength
prepend Limiters::BlockId
TYPE = 'input'

attr_accessor :label, :element, :block_id, :hint, :optional, :emoji
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/rich_text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Layout
#
# https://api.slack.com/reference/block-kit/blocks#rich_text
class RichText
prepend Limiters::BlockId
TYPE = 'rich_text'

attr_accessor :elements
Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#section
class Section
prepend Limiters::BlockId
include Section::MultiSelectElements
TYPE = 'section'

Expand Down
1 change: 1 addition & 0 deletions lib/slack/block_kit/layout/video.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ module Layout
#
# https://api.slack.com/reference/messaging/blocks#context
class Video
prepend Limiters::BlockId
TYPE = 'video'

def initialize(alt_text:, thumbnail_url:, video_url:, title:, description:, **optional_args)
Expand Down
19 changes: 19 additions & 0 deletions lib/slack/block_kit/limiters/block_id.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Slack
module BlockKit
module Limiters
module BlockId
BLOCK_ID_LIMIT = 255

def as_json
json = super

if Slack::BlockKit.config.autofix_invalid_blocks? && json[:block_id]
json[:block_id] = Utils.truncate(json[:block_id].to_s, length: BLOCK_ID_LIMIT, omission: "")
end

json
end
end
end
end
end
23 changes: 23 additions & 0 deletions lib/slack/block_kit/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Slack
module BlockKit
module Utils
# Truncate a string to a certain length, adding an optional omission
# string if the string is truncated. This is generally used to ensure
# that certain block limits do not exceed their maximum length when
# autofixing is enabled.
#
# See: https://api.rubyonrails.org/classes/String.html#method-i-truncate
def self.truncate(text, length:, omission: "...", separator: nil)
return text.dup unless text.length > length

omission ||= ""
length_with_room_for_omission = length - omission.length

stop = text.rindex(separator, length_with_room_for_omission) if separator
stop ||= length_with_room_for_omission

"#{text[0, stop]}#{omission}"
end
Copy link
Author

@davidcelis davidcelis Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I used Rails' implementation of truncate for a couple reasons

  1. Certain things should maybe truncate differently (e.g. block_id and action_id don't need to truncate with an ellipsis at the end, though that's supposedly still valid), so the omission and separator options give us control
  2. I could see us adding configuration options on top of autofix_invalid_blocks that allow users to specify how they'd like text to truncate (e.g. Slack::BlockKit.config.truncate_invalid_text_with = "…" or something)

end
end
end
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/actions_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Actions do
subject(:actions_json) { instance.as_json }
Expand All @@ -20,6 +21,8 @@
expect(actions_json).to eq(expected_json)
end

it_behaves_like 'a block that handles block_id length limits'

describe '#button' do
let(:expected_element_json) do
{
Expand Down
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/context_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Context do
subject(:context_json) { instance.as_json }
Expand All @@ -19,6 +20,8 @@
expect(context_json).to eq(expected_json)
end

it_behaves_like 'a block that handles block_id length limits'

describe '#image' do
let(:expected_json) do
{
Expand Down
31 changes: 31 additions & 0 deletions spec/lib/slack/block_kit/layout/divider_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Divider do
let(:instance) { described_class.new(**params) }

it_behaves_like 'a block that handles block_id length limits'

describe '#as_json' do
subject { instance.as_json }

let(:params) { {} }
let(:expected_json) { { type: 'divider' } }

it { is_expected.to eq expected_json }

context 'with block_id' do
let(:params) { { block_id: '1123' } }
let(:expected_json) do
{
type: 'divider',
block_id: '1123'
}
end

it { is_expected.to eq expected_json }
end
end
end
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/header_spec.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Header do
let(:instance) { described_class.new(**params) }

it_behaves_like 'a block that handles block_id length limits', text: '__TEXT__'

describe '#as_json' do
subject { instance.as_json }

Expand Down
55 changes: 55 additions & 0 deletions spec/lib/slack/block_kit/layout/image_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Image do
let(:instance) { described_class.new(**params) }

it_behaves_like 'a block that handles block_id length limits', url: '__URL__', alt_text: '__ALT_TEXT__'

describe '#as_json' do
subject { instance.as_json }

let(:params) do
{
url: '__URL__',
alt_text: '__ALT_TEXT__'
}
end
let(:expected_json) do
{
type: 'image',
image_url: '__URL__',
alt_text: '__ALT_TEXT__'
}
end

it { is_expected.to eq expected_json }

context 'with all arguments' do
let(:params) do
{
url: '__URL__',
alt_text: '__ALT_TEXT__',
block_id: '1123',
title: 'This is a title',
}
end
let(:expected_json) do
{
type: 'image',
image_url: '__URL__',
alt_text: '__ALT_TEXT__',
block_id: '1123',
title: {
type: 'plain_text',
text: 'This is a title'
}
}
end

it { is_expected.to eq expected_json }
end
end
end
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/input_spec.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Input do
subject(:input_json) { instance.as_json }

it_behaves_like 'a block that handles block_id length limits', label: 'Name', element: Slack::BlockKit::Element::PlainTextInput.new(action_id: '__ACTION_ID__')

let(:instance) { described_class.new(**params) }
let(:optional) { false }
let(:hint) { 'Your Name' }
Expand Down
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/rich_text_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::RichText do
subject(:rich_text_json) { instance.as_json }
Expand All @@ -19,6 +20,8 @@
expect(rich_text_json).to eq(expected_json)
end

it_behaves_like 'a block that handles block_id length limits'

describe '#rich_text_section' do
let(:expected_json) do
{
Expand Down
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/section_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Section do
subject(:section_json) { instance.as_json }
Expand All @@ -26,6 +27,8 @@
expect(section_json).to eq(expected_json)
end

it_behaves_like 'a block that handles block_id length limits'

describe '#mrkdwn' do
let(:expected_json) do
{
Expand Down
3 changes: 3 additions & 0 deletions spec/lib/slack/block_kit/layout/video_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# frozen_string_literal: true

require 'spec_helper'
require_relative '../limiters/block_id_helpers'

RSpec.describe Slack::BlockKit::Layout::Video do
it_behaves_like 'a block that handles block_id length limits', alt_text: 'test', thumbnail_url: 'test', video_url: 'test', title: 'test', description: 'test'

describe '.as_json' do
subject(:video_json) { instance.as_json }

Expand Down
47 changes: 47 additions & 0 deletions spec/lib/slack/block_kit/limiters/block_id_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.shared_examples_for 'a block that handles block_id length limits' do |**params|
let(:block) { described_class.new(block_id: block_id, **params) }
let(:block_id) { 'a' * (Slack::BlockKit::Limiters::BlockId::BLOCK_ID_LIMIT + 1) }

subject(:json) { block.as_json }

context 'when autofix is disabled' do
it 'does not truncate the block_id when converting to JSON' do
expect(json[:block_id]).to eq(block_id)
end
end

context 'when autofix is enabled' do
let(:block) { described_class.new(block_id: block_id, **params) }

around do |example|
Slack::BlockKit.configuration.autofix_invalid_blocks = true
example.run
Slack::BlockKit.configuration.autofix_invalid_blocks = false
end

it 'truncates the block_id when converting to JSON' do
expect(json[:block_id]).to eq('a' * Slack::BlockKit::Limiters::BlockId::BLOCK_ID_LIMIT)
end

context 'when the block_id is already within the limit' do
let(:block_id) { 'a' * (Slack::BlockKit::Limiters::BlockId::BLOCK_ID_LIMIT - 1) }

it 'does not truncate the block_id' do
expect(json[:block_id]).to eq(block_id)
end
end

context 'when no block_id is provided' do
let(:block) { described_class.new(**params) }
let(:block_id) { nil }

it 'does not include block_id in the JSON' do
expect(json).not_to have_key(:block_id)
end
end
end
end
Loading