// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. // Helper function to create dialog base structure function createDialog(triggerId, webhookBaseUrl, dialogConfig) { const baseDialog = { trigger_id: triggerId, url: `${webhookBaseUrl}/dialog_submit`, dialog: { callback_id: dialogConfig.callback_id, title: dialogConfig.title, submit_label: dialogConfig.submit_label || 'Submit', notify_on_cancel: true, ...dialogConfig.dialog_props, elements: dialogConfig.elements || [], }, }; if (dialogConfig.icon_url) { baseDialog.dialog.icon_url = dialogConfig.icon_url; } if (dialogConfig.introduction_text) { baseDialog.dialog.introduction_text = dialogConfig.introduction_text; } if (dialogConfig.state) { baseDialog.dialog.state = dialogConfig.state; } if (dialogConfig.source_url) { baseDialog.dialog.source_url = dialogConfig.source_url; } return baseDialog; } // Helper function to create form response structure function createFormResponse(formConfig) { return { callback_id: formConfig.callback_id, title: formConfig.title, submit_label: formConfig.submit_label || 'Submit', notify_on_cancel: true, elements: formConfig.elements || [], ...formConfig.form_props, }; } // Helper function to create common form elements function createElement(type, config) { const baseElement = { display_name: config.display_name, name: config.name, type, optional: config.optional || false, }; if (config.placeholder) { baseElement.placeholder = config.placeholder; } if (config.help_text) { baseElement.help_text = config.help_text; } if (config.default) { baseElement.default = config.default; } if (config.subtype) { baseElement.subtype = config.subtype; } if (config.min_length) { baseElement.min_length = config.min_length; } if (config.max_length) { baseElement.max_length = config.max_length; } if (config.data_source) { baseElement.data_source = config.data_source; } if (config.options) { baseElement.options = config.options; } if (config.refresh) { baseElement.refresh = config.refresh; } if (config.label_position) { baseElement.label_position = config.label_position; } if (config.matrix_config) { baseElement.matrix_config = config.matrix_config; } if (config.multiselect) { baseElement.multiselect = true; } if (config.action_button) { baseElement.action_button = config.action_button; } return baseElement; } // Standard icon URL const STANDARD_ICON = 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png'; const WAIVER_REASON_OPTIONS = [ {text: 'Reason 1', value: 'reason_1'}, {text: 'Reason 2', value: 'reason_2'}, {text: 'Reason 3', value: 'reason_3'}, ]; const SEVERITY_MATRIX_COLUMNS = [ {text: 'High', value: 'high'}, {text: 'Severe', value: 'severe'}, ]; // Dialog configurations const DIALOG_CONFIGS = { full: { callback_id: 'somecallbackid', title: 'Title for Full Dialog Test', icon_url: STANDARD_ICON, elements: [ createElement('text', {display_name: 'Display Name', name: 'realname', default: 'default text', placeholder: 'placeholder', help_text: 'This a regular input in an interactive dialog triggered by a test integration.'}), createElement('text', {display_name: 'Email', name: 'someemail', subtype: 'email', placeholder: 'placeholder@bladekick.com', help_text: 'This a regular email input in an interactive dialog triggered by a test integration.'}), createElement('text', {display_name: 'Number', name: 'somenumber', subtype: 'number'}), createElement('text', {display_name: 'Password', name: 'somepassword', subtype: 'password', default: 'p@ssW0rd', placeholder: 'placeholder', help_text: 'This a password input in an interactive dialog triggered by a test integration.', optional: true}), createElement('textarea', {display_name: 'Display Name Long Text Area', name: 'realnametextarea', placeholder: 'placeholder', optional: true, min_length: 5, max_length: 100}), createElement('select', {display_name: 'User Selector', name: 'someuserselector', placeholder: 'Select a user...', data_source: 'users'}), createElement('select', {display_name: 'Channel Selector', name: 'somechannelselector', placeholder: 'Select a channel...', help_text: 'Choose a channel from the list.', data_source: 'channels', optional: true}), createElement('select', {display_name: 'Option Selector', name: 'someoptionselector', placeholder: 'Select an option...', options: [{text: 'Option1', value: 'opt1'}, {text: 'Option2', value: 'opt2'}, {text: 'Option3', value: 'opt3'}]}), createElement('radio', {display_name: 'Radio Option Selector', name: 'someradiooptions', help_text: '', options: [{text: 'Engineering', value: 'engineering'}, {text: 'Sales', value: 'sales'}]}), createElement('bool', {display_name: 'Boolean Selector', name: 'boolean_input', placeholder: 'Was this modal helpful?', default: 'True', optional: true, help_text: 'This is the help text'}), ], dialog_props: {state: 'somestate'}, }, simple: { callback_id: 'somecallbackid', title: 'Title for Dialog Test without elements', icon_url: STANDARD_ICON, submit_label: 'Submit Test', dialog_props: {state: 'somestate'}, }, basic: { callback_id: 'basic_dialog_callback', title: 'Basic Dialog', icon_url: STANDARD_ICON, submit_label: 'Submit', introduction_text: 'Required text, optional radio, single select, and optional multiselect.', elements: [ createElement('text', { display_name: 'Title', name: 'title', placeholder: 'Enter a title', help_text: 'Required text field.', }), createElement('radio', { display_name: 'Department', name: 'department', help_text: 'Optional.', optional: true, options: [ {text: 'Engineering', value: 'engineering'}, {text: 'Sales', value: 'sales'}, {text: 'Support', value: 'support'}, ], }), createElement('select', { display_name: 'Priority', name: 'priority', placeholder: 'Choose priority...', help_text: 'Optional single select. Use the × control to clear.', optional: true, options: [ {text: 'Low', value: 'low'}, {text: 'Medium', value: 'medium'}, {text: 'High', value: 'high'}, ], }), createElement('select', { display_name: 'Categories', name: 'categories', multiselect: true, placeholder: 'Select one or more...', help_text: 'Optional multiselect (`type: select` + `multiselect: true`). Submits a string array.', optional: true, options: [ {text: 'Bug', value: 'bug'}, {text: 'Feature', value: 'feature'}, {text: 'Documentation', value: 'docs'}, ], }), ], dialog_props: {state: 'basic_dialog_state'}, }, userAndChannel: { callback_id: 'somecallbackid', title: 'Title for Dialog Test with user and channel element', icon_url: STANDARD_ICON, submit_label: 'Submit Test', elements: [ createElement('select', {display_name: 'User Selector', name: 'someuserselector', placeholder: 'Select a user...', data_source: 'users'}), createElement('select', {display_name: 'Channel Selector', name: 'somechannelselector', placeholder: 'Select a channel...', help_text: 'Choose a channel from the list.', data_source: 'channels', optional: true}), ], dialog_props: {state: 'somestate'}, }, boolean: { callback_id: 'somecallbackid', title: 'Title for Dialog Test with boolean element', icon_url: STANDARD_ICON, submit_label: 'Submit Test', elements: [ createElement('bool', {display_name: 'Boolean Selector', name: 'boolean_input', placeholder: 'Was this modal helpful?', default: 'True', optional: true, help_text: 'This is the help text'}), ], dialog_props: {state: 'somestate'}, }, checkboxGroup: { callback_id: 'checkbox_group_callback', title: 'Checkbox Dialog Feature Demo', icon_url: STANDARD_ICON, submit_label: 'Submit', introduction_text: 'Two checkbox groups (label before vs after), plus related v1 dialog features for manual QA.', elements: [ createElement('checkbox_group', { display_name: 'Group A: label before', name: 'waiver_group_label_before', help_text: 'Required. Option text is left of each checkbox (`label_position: before`). Default: reason_1 + reason_3.', default: 'reason_1,reason_3', label_position: 'before', options: WAIVER_REASON_OPTIONS, }), createElement('checkbox_group', { display_name: 'Group B: label after', name: 'waiver_group_label_after', help_text: 'Optional. Option text is right of each checkbox (`label_position: after`, default). Clear all → submits [].', default: 'reason_2', label_position: 'after', optional: true, options: WAIVER_REASON_OPTIONS, }), createElement('radio', { display_name: 'Department (optional)', name: 'department', help_text: 'Optional radio with default + Clear selection link.', default: 'sales', optional: true, label_position: 'after', options: [ {text: 'Engineering', value: 'engineering'}, {text: 'Sales', value: 'sales'}, {text: 'Administration', value: 'administration'}, ], }), createElement('bool', { display_name: 'Acknowledgement', name: 'acknowledged', placeholder: 'I understand the waiver terms', help_text: 'Bool with label before the checkbox.', label_position: 'before', default: 'false', }), createElement('checkbox_matrix', { display_name: 'Severity (multi per row)', name: 'severity_multiple', help_text: 'Matrix with row_selection: multiple — multiple columns per row allowed.', optional: true, matrix_config: { row_selection: 'multiple', rows: WAIVER_REASON_OPTIONS, columns: SEVERITY_MATRIX_COLUMNS, }, }), createElement('checkbox_matrix', { display_name: 'Priority (one per row)', name: 'priority_single', help_text: 'Matrix with row_selection: single — radio-style, one column per row.', optional: true, matrix_config: { row_selection: 'single', rows: WAIVER_REASON_OPTIONS, columns: [ {text: 'Low', value: 'low'}, {text: 'Medium', value: 'medium'}, {text: 'High', value: 'high'}, ], }, }), ], dialog_props: {state: 'checkbox_group_state'}, }, fieldRefresh: { callback_id: 'field_refresh_callback', title: 'Field Refresh Demo', introduction_text: 'Enter project name then select type to see different fields', elements: [ createElement('text', {display_name: 'Project Name', name: 'project_name', placeholder: 'Enter project name'}), createElement('select', {display_name: 'Project Type', name: 'project_type', refresh: true, placeholder: 'Select project type...', options: [{text: 'Web Application', value: 'web'}, {text: 'Mobile App', value: 'mobile'}, {text: 'API Service', value: 'api'}]}), ], }, multistepStep1: { callback_id: 'multistep_callback', title: 'Step 1 - Personal Info', introduction_text: 'Multi-step registration - Step 1 of 3', submit_label: 'Next Step', elements: [ createElement('text', {display_name: 'First Name', name: 'first_name', placeholder: 'Enter your first name'}), createElement('text', {display_name: 'Email', name: 'email', subtype: 'email', placeholder: 'Enter your email address'}), ], dialog_props: {state: 'step1'}, }, multistepStep2: { callback_id: 'multistep_callback', title: 'Step 2 - Work Info', introduction_text: 'Multi-step registration - Step 2 of 3', submit_label: 'Next Step', elements: [ createElement('select', {display_name: 'Department', name: 'department', placeholder: 'Select department...', options: [{text: 'Engineering', value: 'engineering'}, {text: 'Marketing', value: 'marketing'}, {text: 'Sales', value: 'sales'}]}), createElement('radio', {display_name: 'Experience Level', name: 'experience_level', options: [{text: 'Junior', value: 'junior'}, {text: 'Mid-level', value: 'mid'}, {text: 'Senior', value: 'senior'}]}), ], form_props: {state: 'step2'}, }, multistepStep3: { callback_id: 'multistep_callback', title: 'Step 3 - Final Details', introduction_text: 'Multi-step registration - Step 3 of 3', submit_label: 'Complete Registration', elements: [ createElement('textarea', {display_name: 'Comments', name: 'comments', placeholder: 'Any additional comments...', optional: true}), createElement('bool', {display_name: 'Terms & Conditions', name: 'terms_accepted'}), ], form_props: {state: 'step3'}, }, actionButtonParent: { callback_id: 'action_button_parent_callback', title: 'Parent Dialog with Action Button', elements: [], }, actionButtonChild: { callback_id: 'child_callback', title: 'Child Dialog', elements: [ createElement('text', {display_name: 'Child Input', name: 'child_input', placeholder: 'Enter value', optional: true}), ], }, }; // Public API functions function getFullDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.full); } function getSimpleDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.simple); } function getBasicDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.basic); } function getUserAndChannelDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.userAndChannel); } function getBooleanDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.boolean); } function getCheckboxGroupDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.checkboxGroup); } function getFieldRefreshDialog(triggerId, webhookBaseUrl) { const config = {...DIALOG_CONFIGS.fieldRefresh}; config.source_url = `${webhookBaseUrl}/field_refresh_source`; return createDialog(triggerId, webhookBaseUrl, config); } function getMultistepStep1Dialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, DIALOG_CONFIGS.multistepStep1); } function getMultistepStep2Dialog(triggerId, webhookBaseUrl) { const config = {...DIALOG_CONFIGS.multistepStep2}; config.dialog_props = {url: `${webhookBaseUrl}/dialog_submit`, ...config.form_props}; return createFormResponse(config); } function getMultistepStep3Dialog(triggerId, webhookBaseUrl) { const config = {...DIALOG_CONFIGS.multistepStep3}; config.dialog_props = {url: `${webhookBaseUrl}/dialog_submit`, ...config.form_props}; return createFormResponse(config); } function getMultiSelectDialog(triggerId, webhookBaseUrl, includeDefaults = false) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/dialog_submit`, dialog: { callback_id: 'somecallbackid', title: 'Title for Dialog Test with multiselect elements', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', submit_label: 'Submit Multiselect Test', notify_on_cancel: true, state: 'somestate', elements: [ { display_name: 'Multi Option Selector', name: 'multiselect_options', type: 'select', multiselect: true, default: includeDefaults ? 'opt1,opt3' : '', placeholder: 'Select multiple options...', help_text: 'You can select multiple options from this list.', optional: false, min_length: 0, max_length: 0, data_source: '', options: [ { text: 'Engineering', value: 'opt1', }, { text: 'Sales', value: 'opt2', }, { text: 'Marketing', value: 'opt3', }, { text: 'Support', value: 'opt4', }, { text: 'Product', value: 'opt5', }, ], }, { display_name: 'Multi User Selector', name: 'multiselect_users', type: 'select', multiselect: true, default: '', placeholder: 'Select multiple users...', help_text: 'Choose multiple users from the team.', optional: false, min_length: 0, max_length: 0, data_source: 'users', options: null, }, { display_name: 'Single Option Selector', name: 'single_select_options', type: 'select', multiselect: false, default: includeDefaults ? 'single2' : '', placeholder: 'Select one option...', help_text: 'This is a regular single-select for comparison.', optional: false, min_length: 0, max_length: 0, data_source: '', options: [ { text: 'Single Option 1', value: 'single1', }, { text: 'Single Option 2', value: 'single2', }, { text: 'Single Option 3', value: 'single3', }, ], }, ], }, }; } function getDynamicSelectDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/dialog_submit`, dialog: { callback_id: 'somecallbackid', title: 'Title for Dialog Test with dynamic select element', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', submit_label: 'Submit Dynamic Select Test', notify_on_cancel: true, state: 'somestate', elements: [ { display_name: 'Dynamic Role Selector', name: 'dynamic_role_selector', type: 'select', data_source: 'dynamic', data_source_url: `${webhookBaseUrl}/dynamic_select_source`, default: '', placeholder: 'Search for a role...', help_text: 'Start typing to search for available roles. Options are loaded dynamically.', optional: false, min_length: 0, max_length: 0, }, { display_name: 'Optional Dynamic Selector', name: 'optional_dynamic_selector', type: 'select', data_source: 'dynamic', data_source_url: `${webhookBaseUrl}/dynamic_select_source`, default: 'backend_eng', placeholder: 'Search for another role...', help_text: 'This field is optional and has a default value.', optional: true, min_length: 0, max_length: 0, }, ], }, }; } // Basic date field test - MM-T2530A function getBasicDateDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/datetime_dialog_submit`, dialog: { callback_id: 'basic_date_callback', title: 'DateTime Fields Test', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', elements: [ { display_name: 'Event Date', name: 'event_date', type: 'date', default: '', placeholder: 'Select a date', help_text: 'Select the date for your event', optional: false, }, ], submit_label: 'Submit', notify_on_cancel: true, state: 'datetime_state', }, }; } // Basic datetime field test - MM-T2530B function getBasicDateTimeDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/datetime_dialog_submit`, dialog: { callback_id: 'basic_datetime_callback', title: 'DateTime Fields Test', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', elements: [ { display_name: 'Event Date', name: 'event_date', type: 'date', default: '', placeholder: 'Select a date', help_text: 'Select the date for your event', optional: false, }, { display_name: 'Meeting Time', name: 'meeting_time', type: 'datetime', default: '', placeholder: 'Select date and time', help_text: 'Select the date and time for your meeting', optional: false, datetime_config: { time_interval: 60, }, }, ], submit_label: 'Submit', notify_on_cancel: true, state: 'datetime_state', }, }; } // Date field with min_date constraint - MM-T2530C function getMinDateConstraintDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/datetime_dialog_submit`, dialog: { callback_id: 'mindate_callback', title: 'DateTime Fields Test', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', elements: [ { display_name: 'Future Date Only', name: 'future_date', type: 'date', default: '', placeholder: 'Select a future date', help_text: 'Must be today or later', optional: true, datetime_config: { min_date: 'today', }, }, ], submit_label: 'Submit', notify_on_cancel: true, state: 'datetime_state', }, }; } // DateTime field with custom time interval - MM-T2530D function getCustomIntervalDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/datetime_dialog_submit`, dialog: { callback_id: 'interval_callback', title: 'DateTime Fields Test', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', elements: [ { display_name: 'Custom Interval Time', name: 'interval_time', type: 'datetime', default: '', placeholder: 'Select time (30min intervals)', help_text: 'Time picker with 30-minute intervals', optional: true, datetime_config: { time_interval: 30, }, }, ], submit_label: 'Submit', notify_on_cancel: true, state: 'datetime_state', }, }; } // Relative date values test - MM-T2530F function getRelativeDateDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/datetime_dialog_submit`, dialog: { callback_id: 'relative_callback', title: 'DateTime Fields Test', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', elements: [ { display_name: 'Relative Date Example', name: 'relative_date', type: 'date', default: 'today', placeholder: 'Today by default', help_text: 'Defaults to today using relative date', optional: true, }, { display_name: 'Relative DateTime Example', name: 'relative_datetime', type: 'datetime', default: '+1d', placeholder: 'Tomorrow by default', help_text: 'Defaults to tomorrow using relative date', optional: true, }, ], submit_label: 'Submit', notify_on_cancel: true, state: 'datetime_state', }, }; } // Legacy function for backward compatibility - returns basic datetime dialog function getDateTimeDialog(triggerId, webhookBaseUrl) { return getBasicDateTimeDialog(triggerId, webhookBaseUrl); } function getTimezoneManualDialog(triggerId, webhookBaseUrl) { return createDialog(triggerId, webhookBaseUrl, { callback_id: 'timezone_manual', title: 'Timezone & Manual Entry Demo', introduction_text: '**Timezone & Manual Entry Demo**\n\n' + 'This dialog demonstrates timezone support and manual time entry features.', elements: [ { display_name: 'Your Local Time (Manual Entry)', name: 'local_manual', type: 'datetime', help_text: 'Type any time: 9am, 14:30, 3:45pm - no rounding', datetime_config: { manual_time_entry: true, }, optional: true, }, { display_name: 'London Office Hours (Dropdown)', name: 'london_dropdown', type: 'datetime', help_text: 'Times shown in GMT - select from 60 min intervals', datetime_config: { location_timezone: 'Europe/London', time_interval: 60, }, optional: true, }, { display_name: 'London Office Hours (Manual Entry)', name: 'london_manual', type: 'datetime', help_text: 'Type time in GMT: 9am, 14:30, 3:45pm - no rounding', datetime_config: { location_timezone: 'Europe/London', manual_time_entry: true, }, optional: true, }, ], }); } function getFileUploadDialog(triggerId, webhookBaseUrl) { return { trigger_id: triggerId, url: `${webhookBaseUrl}/dialog_submit`, dialog: { callback_id: 'somecallbackid', title: 'Title for Dialog Test with file upload element', icon_url: 'https://mattermost.com/wp-content/uploads/2022/02/icon_WS.png', submit_label: 'Submit File Upload Test', notify_on_cancel: true, state: 'somestate', elements: [ { display_name: 'Upload Single Document', name: 'single_document', type: 'file', placeholder: 'Select one document...', help_text: 'Upload a single document (replaces previous selection).', optional: false, }, { display_name: 'Upload Multiple Files', name: 'multiple_files', type: 'file', allow_multiple: true, placeholder: 'Select multiple files...', help_text: 'Upload multiple files (can select and add more).', optional: false, }, { display_name: 'Description', name: 'description', type: 'textarea', subtype: '', default: '', placeholder: 'Describe the uploaded files...', help_text: 'Provide a description for the uploaded files.', optional: true, min_length: 0, max_length: 500, data_source: '', options: null, }, ], }, }; } function getActionButtonParentDialog(triggerId, webhookBaseUrl) { const config = { ...DIALOG_CONFIGS.actionButtonParent, elements: [ createElement('text', {display_name: 'Your Name', name: 'your_name', placeholder: 'Enter your name', optional: true}), // Two action buttons on the same dialog. Each carries a distinct // context.source so the child dialog can reflect which one was pressed. createElement('action_button', { display_name: 'Open Details', name: 'open_details', action_button: { url: `${webhookBaseUrl}/dialog/open_child`, context: {source: 'Details'}, }, }), createElement('action_button', { display_name: 'Open Summary', name: 'open_summary', action_button: { url: `${webhookBaseUrl}/dialog/open_child`, context: {source: 'Summary'}, }, }), ], }; return createDialog(triggerId, webhookBaseUrl, config); } // `source` comes from the pressed action button's context.source and is reflected // in the child dialog's title and introduction text, so a test can verify which // button opened it. function getActionButtonChildDialog(triggerId, webhookBaseUrl, source) { const label = source || 'Unknown'; const config = { ...DIALOG_CONFIGS.actionButtonChild, title: `${label} Dialog`, introduction_text: `This child dialog was opened from the "${label}" action button.`, }; return createDialog(triggerId, webhookBaseUrl, config); } module.exports = { getFullDialog, getSimpleDialog, getBasicDialog, getUserAndChannelDialog, getBooleanDialog, getCheckboxGroupDialog, getFieldRefreshDialog, getMultistepStep1Dialog, getMultistepStep2Dialog, getMultistepStep3Dialog, getMultiSelectDialog, getDynamicSelectDialog, getDateTimeDialog, getBasicDateDialog, getBasicDateTimeDialog, getMinDateConstraintDialog, getCustomIntervalDialog, getRelativeDateDialog, getTimezoneManualDialog, getFileUploadDialog, getActionButtonParentDialog, getActionButtonChildDialog, };