From dc01a1cfc304d69bfc11235094b7614d50d7c826 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Fri, 30 Aug 2024 15:50:12 -0400 Subject: [PATCH 1/2] start of splitting out msg attachment --- packages/types/src/message-attachments.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/types/src/message-attachments.ts b/packages/types/src/message-attachments.ts index 9426465ea..e94d592d1 100644 --- a/packages/types/src/message-attachments.ts +++ b/packages/types/src/message-attachments.ts @@ -13,7 +13,11 @@ import { PlainTextElement } from './block-kit/composition-objects'; * Message attachments are considered a legacy part of messaging functionality. They are not deprecated per se, but they may change in the future, in ways that reduce their visibility or utility. We recommend moving to Block Kit instead. Read more about {@link https://api.slack.com/messaging/composing/layouts#when-to-use-attachments when to use message attachments}. * @see {@link https://api.slack.com/reference/messaging/attachments Secondary message attachments reference documentation} */ -export interface MessageAttachment { +export type MessageAttachment = LegacyAttachment | BlocksAttachment; + +interface BlocksAttachment { +} +interface LegacyAttachment { /** * @description An array of {@link KnownBlock layout blocks} in the same format * {@link https://api.slack.com/block-kit/building as described in the building blocks guide}. From 1d4796cbea3aa65dd5a7d6b54e62229b2293efd9 Mon Sep 17 00:00:00 2001 From: Filip Maj Date: Fri, 30 Aug 2024 17:30:46 -0400 Subject: [PATCH 2/2] partway through msg attachments redo --- packages/types/src/message-attachments.ts | 187 +++++++++++------- .../types/test/message-attachments.test-d.ts | 19 ++ 2 files changed, 135 insertions(+), 71 deletions(-) create mode 100644 packages/types/test/message-attachments.test-d.ts diff --git a/packages/types/src/message-attachments.ts b/packages/types/src/message-attachments.ts index e94d592d1..8f0468fae 100644 --- a/packages/types/src/message-attachments.ts +++ b/packages/types/src/message-attachments.ts @@ -1,47 +1,46 @@ import { AnyBlock } from './block-kit/blocks'; import { PlainTextElement } from './block-kit/composition-objects'; -// TODO: breaking changes, use discriminated union for `fallback`, `text` and `block` properties, maybe LegacyAttachment -// vs. BlocksAttachment? as per https://api.slack.com/reference/messaging/attachments#legacy_fields -// "these fields are optional if you're including blocks as above. If you aren't, one of fallback or text are required" -// also further nested discriminated union types that could be helpful here: -// - LegacyAttachmentWithAuthor: if author_name is present, then author_icon and author_link are optional fields -// - LegacyAttachmentWithFooter: if footer is present, then footer_icon is an optional field -// - image_url and thumb_url cannot be used together /** * Add {@link https://api.slack.com/messaging/composing/layouts#attachments secondary attachments} to your messages in Slack. * Message attachments are considered a legacy part of messaging functionality. They are not deprecated per se, but they may change in the future, in ways that reduce their visibility or utility. We recommend moving to Block Kit instead. Read more about {@link https://api.slack.com/messaging/composing/layouts#when-to-use-attachments when to use message attachments}. * @see {@link https://api.slack.com/reference/messaging/attachments Secondary message attachments reference documentation} */ -export type MessageAttachment = LegacyAttachment | BlocksAttachment; +export type MessageAttachment = (LegacyAttachment | BlocksAttachment) & LegacyAttachmentOptionalFields; -interface BlocksAttachment { -} -interface LegacyAttachment { +// Either `text` or `fallback` is required. +type LegacyAttachment = LegacyAttachmentWithText | LegacyAttachmentWithFallback; +interface LegacyAttachmentWithText { /** - * @description An array of {@link KnownBlock layout blocks} in the same format - * {@link https://api.slack.com/block-kit/building as described in the building blocks guide}. + * @description The main body text of the attachment. It can be formatted as plain text, or with + * {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field. + * The content will automatically collapse if it contains 700+ characters or 5+ line breaks, and will display + * a "Show more..." link to expand the content. */ - blocks?: AnyBlock[]; + text: string; +} +interface LegacyAttachmentWithFallback { /** * @description A plain text summary of the attachment used in clients that * don't show formatted text (e.g. mobile notifications). */ - fallback?: string; // either this or text must be defined - /** - * @description Changes the color of the border on the left side of this attachment from the default gray. Can either - * be one of `good` (green), `warning` (yellow), `danger` (red), or any hex color code (eg. `#439FE0`) - */ - color?: 'good' | 'warning' | 'danger' | string; + fallback: string; +} +interface BlocksAttachment { /** - * @description Text that appears above the message attachment block. It can be formatted as plain text, - * or with {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field. + * @description An array of {@link KnownBlock layout blocks} in the same format + * {@link https://api.slack.com/block-kit/building as described in the building blocks guide}. */ - pretext?: string; + blocks: AnyBlock[]; +} + +// author_link and author_icon require author_name +type AttachmentAuthor = AuthorDetails | RequireAuthorName; +interface AuthorDetails { /** * @description Small text used to display the author's name. */ - author_name?: string; + author_name: string; /** * @description A valid URL that will hyperlink the `author_name` text. Will only work if `author_name` is present. */ @@ -51,28 +50,36 @@ interface LegacyAttachment { * Will only work if `author_name` is present. */ author_icon?: string; // author_name must be present - author_subname?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments - /** - * @description Large title text near the top of the attachment. - */ - title?: string; - /** - * @description A valid URL that turns the `title` text into a hyperlink. - */ - title_link?: string; // title must be present +} +interface RequireAuthorName { + author_name?: undefined; + author_link?: never; + author_icon?: never; +} + +// footer_icon requires footer +type Footer = FooterDetails | RequireFooter; +interface FooterDetails { /** - * @description The main body text of the attachment. It can be formatted as plain text, or with - * {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field. - * The content will automatically collapse if it contains 700+ characters or 5+ line breaks, and will display - * a "Show more..." link to expand the content. + * @description Some brief text to help contextualize and identify an attachment. Limited to 300 characters, + * and may be truncated further when displayed to users in environments with limited screen real estate. */ - text?: string; // either this or fallback must be defined + footer: string; /** - * @description An array of {@link MessageAttachmentField} that get displayed in a table-like way - * (see {@link https://api.slack.com/reference/messaging/attachments#example this example}). - * For best results, include no more than 2-3 field objects. + * @description A valid URL to an image file that will be displayed beside the `footer` text. + * Will only work if `footer` is present. We'll render what you provide at 16px by 16px. + * It's best to use an image that is similarly sized. */ - fields?: MessageAttachmentField[]; + footer_icon?: string; +} +interface RequireFooter { + footer?: undefined; + footer_icon?: never; +} + +// image_url and thumb_url cannot be used together +type Image = ImageUrl | ThumbUrl; +interface ImageUrl { /** * @description A valid URL to an image file that will be displayed at the bottom of the attachment. * We support GIF, JPEG, PNG, and BMP formats. @@ -80,6 +87,9 @@ interface LegacyAttachment { * maintaining the original aspect ratio. Cannot be used with `thumb_url`. */ image_url?: string; + thumb_url?: never; +} +interface ThumbUrl { /** * @description A valid URL to an image file that will be displayed as a thumbnail on the right side of * a message attachment. We currently support the following formats: GIF, JPEG, PNG, and BMP. @@ -88,17 +98,46 @@ interface LegacyAttachment { * For best results, please use images that are already 75px by 75px. */ thumb_url?: string; + image_url?: never; +} + +type LegacyAttachmentOptionalFields = Partial & Partial & +Partial & AttachmentAuthor & Footer & Image & AttachmentActions & { + app_id?: string; // may be present on payloads _sent_ by Slack + bot_id?: string; // may be present on payloads _sent_ by Slack + app_unfurl_url?: string; // may be present on payloads _sent_ by Slack /** - * @description Some brief text to help contextualize and identify an attachment. Limited to 300 characters, - * and may be truncated further when displayed to users in environments with limited screen real estate. + * @description Changes the color of the border on the left side of this attachment from the default gray. Can either + * be one of `good` (green), `warning` (yellow), `danger` (red), or any hex color code (eg. `#439FE0`) */ - footer?: string; + color?: 'good' | 'warning' | 'danger' | string; /** - * @description A valid URL to an image file that will be displayed beside the `footer` text. - * Will only work if `footer` is present. We'll render what you provide at 16px by 16px. - * It's best to use an image that is similarly sized. + * @description An array of {@link MessageAttachmentField} that get displayed in a table-like way + * (see {@link https://api.slack.com/reference/messaging/attachments#example this example}). + * For best results, include no more than 2-3 field objects. */ - footer_icon?: string; + fields?: MessageAttachmentField[]; + is_app_unfurl?: boolean; // TODO: not documented in https://api.slack.com/reference/messaging/attachments + /** + * @description Field names that should be {@link https://api.slack.com/reference/surfaces/formatting#basics formatted by `mrkdwn` syntax}. + * The fields that can be formatted in this way include the names of the `fields` property, or + * the `text` or `pretext` properties. + */ + mrkdwn_in?: ('pretext' | 'text' | 'fields')[]; // TODO: I think `fields` here is wrong? instead they should reference field names from `fields` + /** + * @description Text that appears above the message attachment block. It can be formatted as plain text, + * or with {@link https://api.slack.com/reference/surfaces/formatting#basics `mrkdwn`} by including it in the `mrkdwn_in` field. + */ + pretext?: string; + preview?: MessageAttachmentPreview; // https://api.slack.com/methods/chat.unfurl#markdown + /** + * @description Large title text near the top of the attachment. + */ + title?: string; + /** + * @description A valid URL that turns the `title` text into a hyperlink. + */ + title_link?: string; // title must be present /** * @description A Unix timestamp that is used to relate your attachment to a specific time. * The attachment will display the additional timestamp value as part of the attachment's footer. @@ -106,20 +145,7 @@ interface LegacyAttachment { * relative to the present. Form factors, like mobile versus desktop may also transform its rendered appearance. */ ts?: string; - actions?: AttachmentAction[]; // TODO: https://api.slack.com/legacy/message-buttons#crafting_your_message - callback_id?: string; // TODO: https://api.slack.com/legacy/message-buttons#crafting_your_message - /** - * @description Field names that should be {@link https://api.slack.com/reference/surfaces/formatting#basics formatted by `mrkdwn` syntax}. - * The fields that can be formatted in this way include the names of the `fields` property, or - * the `text` or `pretext` properties. - */ - mrkdwn_in?: ('pretext' | 'text' | 'fields')[]; // TODO: I think `fields` here is wrong? instead they should reference field names from `fields` - app_unfurl_url?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments - is_app_unfurl?: boolean; // TODO: not documented in https://api.slack.com/reference/messaging/attachments - app_id?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments - bot_id?: string; // TODO: not documented in https://api.slack.com/reference/messaging/attachments - preview?: MessageAttachmentPreview; // https://api.slack.com/methods/chat.unfurl#markdown TODO: not documented in https://api.slack.com/reference/messaging/attachments, also unclear why this links to chat.unfurl? -} +}; /** * @description A field object to include in a {@link MessageAttachment}. @@ -151,23 +177,42 @@ interface MessageAttachmentPreview { iconUrl?: string; } -interface AttachmentAction { +// Either specify both callback_id and actions, or neither. +type AttachmentActions = { + /** + * @deprecated Legacy attachments with buttons are deprecated. See {@link https://api.slack.com/messaging/attachments-to-blocks transitioning to blocks}. + * @see {@link https://api.slack.com/legacy/message-buttons#crafting_your_message Legacy "Crafting messages with buttons documentation} + */ + actions: [AttachmentAction, ...AttachmentAction[]]; + /** + * @deprecated Legacy attachments with buttons are deprecated. See {@link https://api.slack.com/messaging/attachments-to-blocks transitioning to blocks}. + * @see {@link https://api.slack.com/legacy/message-buttons#crafting_your_message Legacy "Crafting messages with buttons documentation} + */ + callback_id: string; +} | { callback_id?: never; actions?: never; }; +type AttachmentAction = AttachmentButtonAction | AttachmentMenuAction; +interface AttachmentBaseAction { id?: string; + name: string; + text: string; +} +interface AttachmentButtonAction extends AttachmentBaseAction { + type: 'button'; confirm?: Confirmation; - data_source?: 'static' | 'channels' | 'conversations' | 'users' | 'external'; min_query_length?: number; - name?: string; + style?: 'default' | 'primary' | 'danger'; + value?: string; + url?: string; +} +interface AttachmentMenuAction extends AttachmentBaseAction { + type: 'select'; + data_source?: 'static' | 'channels' | 'conversations' | 'users' | 'external'; options?: OptionField[]; option_groups?: { text: string options: OptionField[]; }[]; selected_options?: OptionField[]; - style?: 'default' | 'primary' | 'danger'; - text: string; - type: 'button' | 'select'; - value?: string; - url?: string; } interface OptionField { diff --git a/packages/types/test/message-attachments.test-d.ts b/packages/types/test/message-attachments.test-d.ts new file mode 100644 index 000000000..f9238264d --- /dev/null +++ b/packages/types/test/message-attachments.test-d.ts @@ -0,0 +1,19 @@ +import { expectAssignable, expectError } from 'tsd'; +import { MessageAttachment } from '../src/index'; + +// -- sad path +expectError({}); // if no blocks, either text or fallback is required. +expectError({ fallback: 'hi', author_link: 'https://slack.com' }); // use of author_link requires author_name. +expectError({ fallback: 'hi', author_icon: 'https://slack.com' }); // use of author_icon requires author_name. +expectError({ fallback: 'hi', footer_icon: 'https://slack.com' }); // use of footer_icon requires footer. +expectError({ fallback: 'hi', thumb_url: 'https://slack.com', image_url: 'https://slack.com' }); // cant use both image_url and thumb_url. +expectError({ fallback: 'hi', callback_id: 'hollah' }); // cant use callback_id without actions. +expectError({ fallback: 'hi', actions: [{ name: 'sup', text: 'sup', type: 'button'}] }); // cant use callback_id without actions. +expectError({ fallback: 'hi', callback_id: 'hi', actions: [] }); // must specify at least one action. + +// -- happy path +expectAssignable({ text: 'hi' }); // if no blocks, either text or fallback is required. +expectAssignable({ fallback: 'hi' }); // if no blocks, either text or fallback is required. +expectAssignable({ fallback: 'hi', author_name: 'filmaj', author_icon: 'https://slack.com' }); // use of author_icon requires author_name. +expectAssignable({ fallback: 'hi', author_name: 'filmaj', author_link: 'https://slack.com' }); // use of author_link requires author_name. +expectAssignable({ fallback: 'hi', footer: 'filmaj', footer_icon: 'https://slack.com' }); // use of footer_icon requires footer.