Skip to content

Commit

Permalink
feat: confirmation dialog (#104)
Browse files Browse the repository at this point in the history
  • Loading branch information
cakekindel authored Jul 22, 2020
1 parent 67ad991 commit be6b2bb
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 5 deletions.
183 changes: 183 additions & 0 deletions src/compose/confirm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use serde::{Deserialize, Serialize};
use validator::Validate;

use crate::text;
use crate::val_helpr::ValidationResult;

/// # Confirm Dialog
/// [slack api docs 🔗]
///
/// An object that defines a dialog that provides a confirmation step to any interactive element.
/// This dialog will ask the user to confirm their action by offering a confirm and deny buttons.
///
/// [slack api docs 🔗]: https://api.slack.com/reference/block-kit/composition-objects#confirm
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize, Validate)]
pub struct Confirm {
#[validate(custom = "validate::title")]
title: text::Text,

#[validate(custom = "validate::text")]
text: text::Text,

#[validate(custom = "validate::confirm")]
confirm: text::Text,

#[validate(custom = "validate::deny")]
deny: text::Text,
style: Option<ConfirmStyle>,
}

impl Confirm {
/// Creates a Confirmation Dialog from the required parts.
///
/// # Arguments
///
/// - `title` - A [`plain_text`-only text object 🔗] that defines the dialog's title.
/// Maximum length for this field is 100 characters.
///
/// - `text` - A [text object 🔗] that defines the explanatory text that
/// appears in the confirm dialog.
/// Maximum length for the `text` in this field is 300 characters.
///
/// - `confirm` - A [`plain_text`-only text object 🔗] to define
/// the text of the button that confirms the action.
/// Maximum length for the `text` in this field is 30 characters.
///
/// - `deny` - A [`plain_text`-only text object 🔗] to define
/// the text of the button that cancels the action.
/// Maximum length for the `text` in this field is 30 characters.
///
/// [text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
/// [`plain_text`-only text object 🔗]: https://api.slack.com/reference/block-kit/composition-objects#text
///
/// # Example
/// ```
/// use slack_blocks::compose::Confirm;
/// use slack_blocks::text;
///
/// let dialog = Confirm::from_parts(
/// "Are you sure?",
/// text::Mrkdwn::from("Are you _sure_ you're sure?\nThis action is permanent."),
/// "I'm sure.",
/// "I'm not sure!",
/// );
///
/// // Results in a modal that looks like:
/// // _______________________________
/// // | |
/// // | Are you sure? |
/// // |_______________________________|
/// // | |
/// // | Are you _sure_ you're sure? |
/// // | This action is permanent. |
/// // |_______________________________|
/// // | |
/// // | |I'm not sure!| |I'm sure.| |
/// // |_______________________________|
/// ```
pub fn from_parts(
title: impl Into<text::Plain>,
text: impl Into<text::Text>,
confirm: impl Into<text::Plain>,
deny: impl Into<text::Plain>,
) -> Self {
Self {
title: title.into().into(),
text: text.into(),
confirm: confirm.into().into(),
deny: deny.into().into(),
style: None
}
}

/// Chainable setter method, used to set the **style** of the
/// confirm button of your modal.
///
/// # Arguments
/// - `style` - Defines the color scheme applied to the `confirm` button.
/// A value of `danger` will display the button with a red background on desktop, or red text on mobile.
/// A value of `primary` will display the button with a green background on desktop, or blue text on mobile.
/// If this field is not provided, the default value will be `primary`.
///
/// # Example
/// ```
/// use slack_blocks::compose::{Confirm, ConfirmStyle};
/// use slack_blocks::text;
///
/// let dialog = Confirm::from_parts(
/// "Are you sure?",
/// text::Mrkdwn::from("Are you _sure_ you're sure?\nThis action is permanent."),
/// "I'm sure.",
/// "I'm not sure!",
/// )
/// .with_style(ConfirmStyle::Danger);
/// ```
pub fn with_style(
mut self,
style: ConfirmStyle,
) -> Self {
self.style = Some(style);
self
}

/// Validate that this Confirm composition object
/// agrees with Slack's model requirements
///
/// # Errors
/// - If `from_parts` was called with `title` longer than 100 chars
/// - If `from_parts` was called with `text` longer than 300 chars
/// - If `from_parts` was called with `confirm` longer than 30 chars
/// - If `from_parts` was called with `deny` longer than 30 chars
///
/// # Example
/// ```
/// use slack_blocks::compose::{Confirm, ConfirmStyle};
/// use slack_blocks::text;
///
/// let dialog = Confirm::from_parts(
/// "Are you sure?",
/// text::Mrkdwn::from("Are you _sure_ you're sure?\nThis action is permanent."),
/// "I'm sure.",
/// "I'm not sure! Oh, geez, I just don't know! Help me decide, please??? Gosh, this is scary...",
/// )
/// .with_style(ConfirmStyle::Danger);
///
/// assert_eq!(true, matches!(dialog.validate(), Err(_)));
/// ```
pub fn validate(&self) -> ValidationResult {
Validate::validate(self)
}
}

/// The possible styles of the confirm button on your dialog.
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfirmStyle {
/// Display the button with a red background on desktop,
/// or red text on mobile.
Danger,
/// Display the button with a green background on desktop,
/// or blue text on mobile.
Primary,
}

mod validate {
use crate::text;
use crate::val_helpr::*;

pub fn text(text: &text::Text) -> ValidatorResult {
below_len("Confirmation Dialog text", 300, text.as_ref())
}

pub fn title(text: &text::Text) -> ValidatorResult {
below_len("Confirmation Dialog title", 100, text.as_ref())
}

pub fn confirm(text: &text::Text) -> ValidatorResult {
below_len("Confirmation Dialog confirmation text", 30, text.as_ref())
}

pub fn deny(text: &text::Text) -> ValidatorResult {
below_len("Confirmation Dialog deny text", 30, text.as_ref())
}
}
3 changes: 3 additions & 0 deletions src/compose/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ pub use opt::Opt;
pub mod opt_group;
pub use opt_group::OptGroup;

pub mod confirm;
pub use confirm::{Confirm, ConfirmStyle};

pub mod conversation_filter;
pub use conversation_filter::ConversationFilter;
19 changes: 14 additions & 5 deletions tests/json.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use slack_blocks::block_elements::BlockElement;
use slack_blocks::blocks::Block;
use slack_blocks::compose::{ConversationFilter, Opt, OptGroup, Text};
use slack_blocks::compose;

macro_rules! happy_json_test {
($name:ident, $test_data:expr => $matches:pat) => {
Expand All @@ -26,10 +26,11 @@ happy_json_test!(divider, test_data::DIVIDER_JSON => Block::Divider { .. } );
happy_json_test!(input, test_data::INPUT_JSON => Block::Input { .. });
happy_json_test!(file, test_data::FILE_JSON => Block::File { .. });

happy_json_test!(option, test_data::OPT_JSON => Opt::<()> { .. });
happy_json_test!(option_group, test_data::OPT_GROUP_JSON => OptGroup::<()> { .. });
happy_json_test!(text, test_data::MRKDWN_TEXT_JSON => Text::Mrkdwn { .. });
happy_json_test!(conv_filter, test_data::CONV_FILTER_JSON => ConversationFilter { .. });
happy_json_test!(option, test_data::OPT_JSON => compose::Opt::<()> { .. });
happy_json_test!(option_group, test_data::OPT_GROUP_JSON => compose::OptGroup::<()> { .. });
happy_json_test!(conv_filter, test_data::CONV_FILTER_JSON => compose::ConversationFilter { .. });
happy_json_test!(confirm, test_data::CONFIRM_DIALOG => compose::Confirm { .. });
happy_json_test!(text, test_data::MRKDWN_TEXT_JSON => compose::Text::Mrkdwn { .. });

happy_json_test!(button, test_data::BUTTON_JSON => BlockElement::Button { .. });

Expand Down Expand Up @@ -115,5 +116,13 @@ mod test_data {
"exclude_bot_users": true,
"exclude_external_shared_channels": true,
});

pub static ref CONFIRM_DIALOG: serde_json::Value = serde_json::json!({
"title": SAMPLE_TEXT_PLAIN.clone(),
"text": SAMPLE_TEXT_PLAIN.clone(),
"confirm": SAMPLE_TEXT_PLAIN.clone(),
"deny": SAMPLE_TEXT_PLAIN.clone(),
"style": "danger"
});
}
}
42 changes: 42 additions & 0 deletions tests/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use slack_blocks::{
block_elements, block_elements::BlockElement, blocks::actions, blocks::context, blocks::file,
blocks::image, blocks::input, blocks::section, blocks::Block, compose::text, compose::Opt,
compose::OptGroup,
compose,
compose::conversation_filter::ConversationKind,
compose::ConversationFilter,
};
Expand Down Expand Up @@ -166,6 +167,47 @@ should_fail!(

// # Composition Objects

// ## Confirm Dialog
should_fail!(
confirm_dialog_with_long_title:
compose::Confirm::from_parts(
common::string_of_len(101),
text::Plain::from(""),
"",
""
)
);

should_fail!(
confirm_dialog_with_long_text:
compose::Confirm::from_parts(
"",
text::Plain::from(common::string_of_len(301)),
"",
""
)
);

should_fail!(
confirm_dialog_with_long_confirm:
compose::Confirm::from_parts(
"",
text::Plain::from(""),
common::string_of_len(31),
""
)
);

should_fail!(
confirm_dialog_with_long_deny:
compose::Confirm::from_parts(
"",
text::Plain::from(""),
"",
common::string_of_len(31),
)
);

// ## Conversation Filter
should_fail!(
conv_filter_with_no_include:
Expand Down

0 comments on commit be6b2bb

Please sign in to comment.