From 51fd952ae6fe02555b7ed1b7b05d9e97f1e90c09 Mon Sep 17 00:00:00 2001 From: Vishal Date: Fri, 15 May 2026 16:33:54 +0530 Subject: [PATCH 1/3] MM-67771: Update Report a Problem to email flow (#35900) * MM-67771 Update Report a Problem to email flow for licensed servers Change the default "Report a Problem" behavior for licensed servers to open a mailto link to reportaproblem@mattermost.com with pre-filled metadata instead of redirecting to the support portal. Unlicensed servers continue to redirect to the troubleshooting forums. Admin console help text is now license-aware with separate descriptions for each plan type. * Add isFreeEdition check for Report a Problem flow Treat both unlicensed servers and licensed servers with entry SKU as free edition. This affects the Report a Problem default behavior (forum redirect vs mailto) and the admin console help text shown. - Add isFreeEdition to general.ts selectors and admin_definition_helpers - Add SKUEntry constant to general constants - Reuse isFreeEdition in product_menu.tsx - Add entry SKU test case for report_a_problem * Add the link to forums for free edition * Add permission and restricted-mode guards to ReportAProblemType dropdown The ReportAProblemType dropdown was missing the write-permission check and RestrictSystemAdmin guard that all other fields in the section have. --- .../link_customization_cloud_spec.js | 2 +- .../customization_not_cloud_spec.js | 10 +- e2e-tests/cypress/tests/utils/constants.js | 2 - .../admin_console/admin_definition.tsx | 74 +++++----- .../admin_definition_helpers.tsx | 1 + .../product_menu/product_menu.tsx | 7 +- webapp/channels/src/i18n/en.json | 13 +- .../mattermost-redux/src/constants/general.ts | 1 + .../src/selectors/entities/general.ts | 5 + .../entities/report_a_problem.test.ts | 128 +++++++++++++++++- .../selectors/entities/report_a_problem.ts | 49 ++++++- .../src/utils/browser_info.ts | 11 ++ 12 files changed, 234 insertions(+), 69 deletions(-) diff --git a/e2e-tests/cypress/tests/integration/channels/system_console/site_configuration/link_customization_cloud_spec.js b/e2e-tests/cypress/tests/integration/channels/system_console/site_configuration/link_customization_cloud_spec.js index 47012ca7261..88732976cd2 100644 --- a/e2e-tests/cypress/tests/integration/channels/system_console/site_configuration/link_customization_cloud_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/system_console/site_configuration/link_customization_cloud_spec.js @@ -28,7 +28,7 @@ describe('SupportSettings', () => { [ {text: 'Ask the community', link: SupportSettings.ASK_COMMUNITY_LINK}, {text: 'Mattermost user guide', link: SupportSettings.MATTERMOST_USER_GUIDE}, - {text: 'Report a problem', link: SupportSettings.REPORT_A_PROBLEM_LINK}, + {text: 'Report a problem', link: 'mailto:reportaproblem@mattermost.com'}, {text: 'Keyboard shortcuts'}, ].forEach(({text, link}) => { if (link) { diff --git a/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_not_cloud_spec.js b/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_not_cloud_spec.js index 60e0d1ad6b2..07cad0354b4 100644 --- a/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_not_cloud_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/system_console/ui_and_api/customization_not_cloud_spec.js @@ -28,14 +28,14 @@ describe('Customization', () => { }); it('MM-T1214 - Can change Report a Problem Link setting', () => { - // * Verify Report a Problem link label is visible and matches the text - cy.findByTestId('SupportSettings.ReportAProblemLinklabel').scrollIntoView().should('be.visible').and('have.text', 'Report a Problem Link:'); + // # Select 'Custom link' from the Report a Problem dropdown + cy.findByTestId('SupportSettings.ReportAProblemTypedropdown').scrollIntoView().select('Custom link'); - // * Verify Report a Problem link input box has default value. The default value depends on the setup before running the test. - cy.findByTestId('SupportSettings.ReportAProblemLinkinput').should('have.value', origConfig.SupportSettings.ReportAProblemLink); + // * Verify Report a Problem link label is visible and matches the text + cy.findByTestId('SupportSettings.ReportAProblemLinklabel').scrollIntoView().should('be.visible').and('have.text', 'Custom Report a Problem Link:'); // * Verify Report a Problem link help text is visible and matches the text - cy.findByTestId('SupportSettings.ReportAProblemLinkhelp-text').find('span').should('be.visible').and('have.text', 'The URL for the Report a Problem link in the Help Menu. If this field is empty, the link is removed from the Help Menu.'); + cy.findByTestId('SupportSettings.ReportAProblemLinkhelp-text').find('span').should('be.visible').and('have.text', 'Enter the URL that users will be directed to when they choose "Report a Problem".'); // # Enter a problem link const reportAProblemLink = 'https://mattermost.com/pl/report-a-bug'; diff --git a/e2e-tests/cypress/tests/utils/constants.js b/e2e-tests/cypress/tests/utils/constants.js index 321e798b74b..19518fec585 100644 --- a/e2e-tests/cypress/tests/utils/constants.js +++ b/e2e-tests/cypress/tests/utils/constants.js @@ -6,7 +6,6 @@ export const ABOUT_LINK = 'https://mattermost.com/pl/about-mattermost'; export const ASK_COMMUNITY_LINK = 'https://mattermost.com/pl/default-ask-mattermost-community/'; export const HELP_LINK = 'https://mattermost.com/pl/help/'; export const PRIVACY_POLICY_LINK = 'https://mattermost.com/pl/privacy-policy/'; -export const REPORT_A_PROBLEM_LINK = 'https://mattermost.com/pl/report-a-bug'; export const TERMS_OF_SERVICE_LINK = 'https://mattermost.com/pl/terms-of-use/'; export const MATTERMOST_USER_GUIDE = 'https://docs.mattermost.com/guides/use-mattermost.html'; @@ -24,7 +23,6 @@ export const SupportSettings = { ASK_COMMUNITY_LINK, HELP_LINK, PRIVACY_POLICY_LINK, - REPORT_A_PROBLEM_LINK, TERMS_OF_SERVICE_LINK, MATTERMOST_USER_GUIDE, }; diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx index 29b930deec1..a62ea3cd26c 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition.tsx @@ -219,6 +219,25 @@ const SAML_SETTINGS_CANONICAL_ALGORITHM_C14N11 = 'Canonical1.1'; // - remove_action: An store action to remove the file. // - fileType: A list of extensions separated by ",". E.g. ".jpg,.png,.gif". +const reportAProblemTypeOptions = [ + { + display_name: defineMessage({id: 'admin.support.problemType.defaultLink', defaultMessage: 'Default'}), + value: 'default', + }, + { + display_name: defineMessage({id: 'admin.support.problemType.email', defaultMessage: 'Email address'}), + value: 'email', + }, + { + display_name: defineMessage({id: 'admin.support.problemType.customLink', defaultMessage: 'Custom link'}), + value: 'link', + }, + { + display_name: defineMessage({id: 'admin.support.problemType.hide', defaultMessage: 'Hide link'}), + value: 'hidden', + }, +]; + const adminDefinitionMessages = defineMessages({ data_retention_title: {id: 'admin.data_retention.title', defaultMessage: 'Data Retention Policy'}, ip_filtering_title: {id: 'admin.sidebar.ip_filtering', defaultMessage: 'IP Filtering'}, @@ -2492,57 +2511,32 @@ const AdminDefinition: AdminDefinitionType = { type: 'dropdown', key: 'SupportSettings.ReportAProblemType', label: defineMessage({id: 'admin.support.reportAProblemTypeTitle', defaultMessage: 'Report a Problem:'}), - help_text: defineMessage({id: 'admin.support.reportAProblemTypeDescription', defaultMessage: 'Select how the ‘Report a Problem’ option behaves. Choosing ‘Custom link’ or ‘Email address’ allows you to provide a URL or address in the next field. ‘Hide link’ removes the ‘Report a Problem’ option from the app.'}), - options: [ - { - display_name: defineMessage({id: 'admin.support.problemType.defaultLink', defaultMessage: 'Default link'}), - value: 'default', - }, - { - display_name: defineMessage({id: 'admin.support.problemType.email', defaultMessage: 'Email address'}), - value: 'email', - }, - { - display_name: defineMessage({id: 'admin.support.problemType.customLink', defaultMessage: 'Custom link'}), - value: 'link', - }, - { - display_name: defineMessage({id: 'admin.support.problemType.hide', defaultMessage: 'Hide link'}), - value: 'hidden', - }, - ], - }, - { - type: 'text', - key: 'defaultLicensedReportAProblemLink', - label: defineMessage({id: 'admin.support.reportAProblemDefaultLinkTitle', defaultMessage: 'Default Report a Problem Link:'}), - help_text: defineMessage({id: 'admin.support.reportAProblemDefaultLinkDescription', defaultMessage: 'Users will be directed to this link when they choose ‘Report a Problem’.'}), - default: 'https://mattermost.com/pl/report_a_problem_licensed', - isDisabled: it.all(), + help_text: defineMessage({id: 'admin.support.reportAProblemTypeDescriptionLicensed', defaultMessage: 'By default, selecting "Report a Problem" from the help menu opens a pre-filled email draft to the Mattermost technical support team. You may provide a custom URL or email address for end user support by choosing "Custom link" or "Email address". "Hide link" removes the "Report a Problem" option from the app.'}), + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION)), isHidden: it.any( + it.isFreeEdition, it.configIsTrue('ExperimentalSettings', 'RestrictSystemAdmin'), - it.not(it.stateMatches('SupportSettings.ReportAProblemType', /default/)), - it.not(it.licensed), ), + options: reportAProblemTypeOptions, }, { - type: 'text', - key: 'defaultUnlicensedReportAProblemLink', - label: defineMessage({id: 'admin.support.reportAProblemDefaultLinkTitle', defaultMessage: 'Default Report a Problem Link:'}), - help_text: defineMessage({id: 'admin.support.reportAProblemDefaultLinkDescription', defaultMessage: 'Users will be directed to this link when they choose ‘Report a Problem’.'}), - default: 'https://mattermost.com/pl/report_a_problem_unlicensed', - isDisabled: it.all(), + type: 'dropdown', + key: 'SupportSettings.ReportAProblemType', + label: defineMessage({id: 'admin.support.reportAProblemTypeTitle', defaultMessage: 'Report a Problem:'}), + help_text: defineMessage({id: 'admin.support.reportAProblemTypeDescriptionUnlicensed', defaultMessage: 'By default, selecting "Report a Problem" from the help menu opens the [Mattermost troubleshooting forums](https://mattermost.com/pl/report_a_problem_unlicensed). You may provide a custom URL or email address for end user support by choosing "Custom link" or "Email address". "Hide link" removes the "Report a Problem" option from the app.'}), + help_text_markdown: true, + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION)), isHidden: it.any( + it.not(it.isFreeEdition), it.configIsTrue('ExperimentalSettings', 'RestrictSystemAdmin'), - it.not(it.stateMatches('SupportSettings.ReportAProblemType', /default/)), - it.licensed, ), + options: reportAProblemTypeOptions, }, { type: 'text', key: 'SupportSettings.ReportAProblemLink', label: defineMessage({id: 'admin.support.reportAProblemLinkTitle', defaultMessage: 'Custom Report a Problem Link:'}), - help_text: defineMessage({id: 'admin.support.reportAProblemLinkDescription', defaultMessage: 'Enter the URL that users will be directed to when they choose ‘Report a Problem’.'}), + help_text: defineMessage({id: 'admin.support.reportAProblemLinkDescription', defaultMessage: 'Enter the URL that users will be directed to when they choose "Report a Problem".'}), isDisabled: it.any( it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION)), ), @@ -2561,7 +2555,7 @@ const AdminDefinition: AdminDefinitionType = { type: 'text', key: 'SupportSettings.ReportAProblemMail', label: defineMessage({id: 'admin.support.reportAProblemEmailTitle', defaultMessage: 'Report a Problem Email Address:'}), - help_text: defineMessage({id: 'admin.support.reportAProblemEmailDescription', defaultMessage: 'Enter the email address that users will be prompted to send a message to when they choose ‘Report a Problem’.'}), + help_text: defineMessage({id: 'admin.support.reportAProblemEmailDescription', defaultMessage: 'Enter the email address that users will be prompted to send a message to when they choose "Report a Problem".'}), isDisabled: (it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION))), isHidden: it.any( it.configIsTrue('ExperimentalSettings', 'RestrictSystemAdmin'), @@ -2578,7 +2572,7 @@ const AdminDefinition: AdminDefinitionType = { type: 'bool', key: 'SupportSettings.AllowDownloadLogs', label: defineMessage({id: 'admin.support.problemAllowDownloadTitle', defaultMessage: 'Allow Mobile App Log Downloads:'}), - help_text: defineMessage({id: 'admin.support.problemAllowDownloadDescription', defaultMessage: 'When enabled, users can download app logs for troubleshooting. If a ‘Report a Problem’ link is shown, logs can be downloaded as part of that flow; if the ‘Report a Problem’ link is hidden, logs remain accessible as a separate option.'}), + help_text: defineMessage({id: 'admin.support.problemAllowDownloadDescription', defaultMessage: 'When enabled, users can download app logs for troubleshooting. If a "Report a Problem" link is shown, logs can be downloaded as part of that flow; if the "Report a Problem" link is hidden, logs remain accessible as a separate option.'}), isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.CUSTOMIZATION)), }, { diff --git a/webapp/channels/src/components/admin_console/admin_definition_helpers.tsx b/webapp/channels/src/components/admin_console/admin_definition_helpers.tsx index 9617bcbc180..e4c15053bd5 100644 --- a/webapp/channels/src/components/admin_console/admin_definition_helpers.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition_helpers.tsx @@ -48,6 +48,7 @@ export const it = { configContains: (group: keyof Partial, setting: string, word: string) => (config: Partial) => Boolean((config[group] as any)?.[setting]?.includes(word)), enterpriseReady: (config: Partial, state: any, license?: ClientLicense, enterpriseReady?: boolean) => Boolean(enterpriseReady), licensed: (config: Partial, state: any, license?: ClientLicense) => license?.IsLicensed === 'true', + isFreeEdition: (config: Partial, state: any, license?: ClientLicense) => license?.IsLicensed !== 'true' || license?.SkuShortName === LicenseSkus.Entry, cloudLicensed: (config: Partial, state: any, license?: ClientLicense) => Boolean(license?.IsLicensed && isCloudLicense(license)), licensedForFeature: (feature: string) => (config: Partial, state: any, license?: ClientLicense) => Boolean(license?.IsLicensed && license[feature] === 'true'), licensedForSku: (skuName: string) => (config: Partial, state: any, license?: ClientLicense) => Boolean(license?.IsLicensed && license.SkuShortName === skuName), diff --git a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx index ddda1781a4b..a01733fee7c 100644 --- a/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx +++ b/webapp/channels/src/components/global_header/left_controls/product_menu/product_menu.tsx @@ -10,7 +10,7 @@ import { ProductsIcon, } from '@mattermost/compass-icons/components'; -import {getLicense} from 'mattermost-redux/selectors/entities/general'; +import {isFreeEdition as isFreeEditionSelector} from 'mattermost-redux/selectors/entities/general'; import {setProductMenuSwitcherOpen} from 'actions/views/product_menu'; import {isSwitcherOpen} from 'selectors/views/product_menu'; @@ -24,7 +24,6 @@ import { import Menu from 'components/widgets/menu/menu'; import MenuWrapper from 'components/widgets/menu/menu_wrapper'; -import {LicenseSkus} from 'utils/constants'; import {useCurrentProductId, useProducts, isChannels} from 'utils/products'; import ProductBranding from './product_branding'; @@ -77,7 +76,7 @@ const ProductMenu = (): JSX.Element => { const switcherOpen = useSelector(isSwitcherOpen); const menuRef = useRef(null); const currentProductID = useCurrentProductId(); - const license = useSelector(getLicense); + const isFreeEdition = useSelector(isFreeEditionSelector); const handleClick = () => dispatch(setProductMenuSwitcherOpen(!switcherOpen)); @@ -114,8 +113,6 @@ const ProductMenu = (): JSX.Element => { ); }); - const isFreeEdition = license.IsLicensed === 'false' || license.SkuShortName === LicenseSkus.Entry; - return (