diff --git a/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap b/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap index d5ff073791..5863912c1c 100644 --- a/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap +++ b/webapp/channels/src/components/actions_menu/__snapshots__/actions_menu.test.tsx.snap @@ -39,8 +39,9 @@ exports[`components/actions_menu/ActionsMenu has actions - marketplace disabled key="the_component_id_pluginmenuitem" onClick={[Function]} show={true} + text="Some text" /> - - { @@ -21,11 +21,13 @@ jest.mock('utils/utils', () => { }; }); -const dropdownComponents: PluginComponent[] = [ +const dropdownComponents: PostDropdownMenuAction[] = [ { id: 'the_component_id', pluginId: 'playbooks', + text: 'Some text', action: jest.fn(), + filter: () => true, }, ]; @@ -39,7 +41,7 @@ describe('components/actions_menu/ActionsMenu', () => { isSysAdmin: true, pluginMenuItems: [], post: TestHelper.getPostMock({id: 'post_id_1', is_pinned: false, type: '' as PostType}), - components: {}, + pluginMenuItemComponents: [], location: 'center', canOpenMarketplace: false, actions: { @@ -117,9 +119,7 @@ describe('components/actions_menu/ActionsMenu', () => { expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(false); wrapper.setProps({ - components: { - [PLUGGABLE_COMPONENT]: dropdownComponents, - }, + pluginMenuItemComponents: dropdownComponents, canOpenMarketplace: true, }); expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(true); @@ -135,9 +135,7 @@ describe('components/actions_menu/ActionsMenu', () => { expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(false); wrapper.setProps({ - components: { - [PLUGGABLE_COMPONENT]: dropdownComponents, - }, + pluginMenuItemComponents: dropdownComponents, }); expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(false); }); diff --git a/webapp/channels/src/components/actions_menu/actions_menu.tsx b/webapp/channels/src/components/actions_menu/actions_menu.tsx index 44051a614b..13cb46f60a 100644 --- a/webapp/channels/src/components/actions_menu/actions_menu.tsx +++ b/webapp/channels/src/components/actions_menu/actions_menu.tsx @@ -27,12 +27,12 @@ import * as PostUtils from 'utils/post_utils'; import type {ModalData} from 'types/actions'; import type {HandleBindingClick, OpenAppsModal, PostEphemeralCallResponseForPost} from 'types/apps'; -import type {PluginComponent} from 'types/store/plugins'; - -import './actions_menu.scss'; +import type {PostDropdownMenuAction, PostDropdownMenuItemComponent} from 'types/store/plugins'; import {ActionsMenuIcon} from './actions_menu_icon'; +import './actions_menu.scss'; + const MENU_BOTTOM_MARGIN = 80; export const PLUGGABLE_COMPONENT = 'PostDropdownMenuItem'; @@ -44,7 +44,7 @@ export type Props = { isMenuOpen?: boolean; isSysAdmin: boolean; location?: 'CENTER' | 'RHS_ROOT' | 'RHS_COMMENT' | 'SEARCH' | string; - pluginMenuItems?: PluginComponent[]; + pluginMenuItems?: PostDropdownMenuAction[]; post: Post; teamId: string; canOpenMarketplace: boolean; @@ -52,9 +52,7 @@ export type Props = { /** * Components for overriding provided by plugins */ - components: { - [componentName: string]: PluginComponent[]; - }; + pluginMenuItemComponents: PostDropdownMenuItemComponent[]; actions: { @@ -346,7 +344,7 @@ export class ActionMenuClass extends React.PureComponent { let menuItems; const hasApps = Boolean(appBindings.length); - const hasPluggables = Boolean(this.props.components[PLUGGABLE_COMPONENT]?.length); + const hasPluggables = Boolean(this.props.pluginMenuItemComponents?.length); const hasPluginItems = Boolean(pluginItems?.length); const hasPluginMenuItems = hasPluginItems || hasApps || hasPluggables; diff --git a/webapp/channels/src/components/actions_menu/actions_menu_empty.test.tsx b/webapp/channels/src/components/actions_menu/actions_menu_empty.test.tsx index 0613ccade0..bc309fdce1 100644 --- a/webapp/channels/src/components/actions_menu/actions_menu_empty.test.tsx +++ b/webapp/channels/src/components/actions_menu/actions_menu_empty.test.tsx @@ -28,7 +28,6 @@ describe('components/actions_menu/ActionsMenu returning empty ("")', () => { test('should match snapshot, return empty ("") on Center', () => { const baseProps: Omit = { post: TestHelper.getPostMock({id: 'post_id_1'}), - components: {}, teamId: 'team_id_1', actions: { openModal: jest.fn(), @@ -42,6 +41,7 @@ describe('components/actions_menu/ActionsMenu returning empty ("")', () => { appsEnabled: false, isSysAdmin: true, canOpenMarketplace: false, + pluginMenuItemComponents: [], }; const wrapper = shallow( diff --git a/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx b/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx index dae362128e..ac67550ee0 100644 --- a/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx +++ b/webapp/channels/src/components/actions_menu/actions_menu_mobile.test.tsx @@ -28,7 +28,6 @@ describe('components/actions_menu/ActionsMenu on mobile view', () => { test('should match snapshot', () => { const baseProps: Omit = { post: TestHelper.getPostMock({id: 'post_id_1'}), - components: {}, teamId: 'team_id_1', actions: { openModal: jest.fn(), @@ -42,6 +41,7 @@ describe('components/actions_menu/ActionsMenu on mobile view', () => { appsEnabled: false, isSysAdmin: true, canOpenMarketplace: false, + pluginMenuItemComponents: [], }; const wrapper = shallow( diff --git a/webapp/channels/src/components/actions_menu/index.ts b/webapp/channels/src/components/actions_menu/index.ts index d1f0824050..d89685a056 100644 --- a/webapp/channels/src/components/actions_menu/index.ts +++ b/webapp/channels/src/components/actions_menu/index.ts @@ -60,7 +60,7 @@ function mapStateToProps(state: GlobalState, ownProps: Props) { return { appBindings, appsEnabled: apps, - components: state.plugins.components, + pluginMenuItemComponents: state.plugins.components.PostDropdownMenuItem, isSysAdmin, pluginMenuItems: state.plugins.components.PostDropdownMenu, teamId: getCurrentTeamId(state), diff --git a/webapp/channels/src/components/app_bar/app_bar.test.tsx b/webapp/channels/src/components/app_bar/app_bar.test.tsx index 3225145392..d85f1ec7ab 100644 --- a/webapp/channels/src/components/app_bar/app_bar.test.tsx +++ b/webapp/channels/src/components/app_bar/app_bar.test.tsx @@ -12,27 +12,28 @@ import mergeObjects from 'packages/mattermost-redux/test/merge_objects'; import {renderWithContext, screen} from 'tests/react_testing_utils'; import {TestHelper} from 'utils/test_helper'; -import type {PluginComponent} from 'types/store/plugins'; +import type {ChannelHeaderButtonAction, RightHandSidebarComponent} from 'types/store/plugins'; import AppBar from './app_bar'; describe('components/app_bar/app_bar', () => { - const channelHeaderComponents: PluginComponent[] = [ + const channelHeaderComponents: ChannelHeaderButtonAction[] = [ { id: 'the_component_id', pluginId: 'playbooks', icon: 'fallback_component' as any, tooltipText: 'Playbooks Tooltip', action: jest.fn(), + dropdownText: 'Playbooks dropdown', }, ]; - const rhsComponents: PluginComponent[] = [ + const rhsComponents: RightHandSidebarComponent[] = [ { id: 'the_rhs_plugin_component_id', pluginId: 'playbooks', - icon:
, - action: jest.fn(), + component: () => null, + title: 'some title', }, ]; @@ -61,7 +62,7 @@ describe('components/app_bar/app_bar', () => { AppBar: channelHeaderComponents, RightHandSidebarComponent: rhsComponents, Product: [], - } as {[componentName: string]: PluginComponent[]}, + }, }, entities: { apps: { diff --git a/webapp/channels/src/components/app_bar/app_bar.tsx b/webapp/channels/src/components/app_bar/app_bar.tsx index 0820204c11..ea7a183206 100644 --- a/webapp/channels/src/components/app_bar/app_bar.tsx +++ b/webapp/channels/src/components/app_bar/app_bar.tsx @@ -3,7 +3,6 @@ import partition from 'lodash/partition'; import React from 'react'; -import type {ReactNode} from 'react'; import {useSelector} from 'react-redux'; import type {GlobalState} from '@mattermost/types/store'; @@ -20,7 +19,7 @@ import {useCurrentProduct, useCurrentProductId, inScope} from 'utils/products'; import AppBarBinding, {isAppBinding} from './app_bar_binding'; import AppBarMarketplace from './app_bar_marketplace'; -import AppBarPluginComponent, {isAppBarPluginComponent} from './app_bar_plugin_component'; +import AppBarPluginComponent, {isAppBarComponent} from './app_bar_plugin_component'; import './app_bar.scss'; @@ -49,7 +48,7 @@ export default function AppBar() { return coreProductsPluginIds.includes(pluginId); }); - const items: ReactNode[] = [ + const items = [ ...coreProductComponents, getDivider(coreProductComponents.length, (pluginComponents.length + channelHeaderComponents.length + appBarBindings.length)), ...pluginComponents, @@ -60,8 +59,9 @@ export default function AppBar() { return x; } - if (isAppBarPluginComponent(x)) { - if (!inScope(x.supportedProductIds ?? null, currentProductId, currentProduct?.pluginId)) { + if (isAppBarComponent(x)) { + const supportedProductIds = 'supportedProductIds' in x ? x.supportedProductIds : undefined; + if (!inScope(supportedProductIds ?? null, currentProductId, currentProduct?.pluginId)) { return null; } return ( diff --git a/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx b/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx index 9656de46be..a99e0a20a2 100644 --- a/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx +++ b/webapp/channels/src/components/app_bar/app_bar_plugin_component.tsx @@ -14,12 +14,12 @@ import WithTooltip from 'components/with_tooltip'; import {suitePluginIds} from 'utils/constants'; -import type {PluginComponent, AppBarComponent} from 'types/store/plugins'; +import type {AppBarAction, ChannelHeaderButtonAction} from 'types/store/plugins'; import NewChannelWithBoardTourTip from './new_channel_with_board_tour_tip'; -type PluginComponentProps = { - component: AppBarComponent; +type AppBarComponentProps = { + component: ChannelHeaderButtonAction | AppBarAction; } enum ImageLoadState { @@ -28,22 +28,27 @@ enum ImageLoadState { ERROR = 'error', } -export const isAppBarPluginComponent = (x: Record | undefined): x is PluginComponent => { +export const isAppBarComponent = (x: Record | undefined): x is (ChannelHeaderButtonAction | AppBarAction) => { return Boolean(x?.id && x?.pluginId); }; -const AppBarPluginComponent = (props: PluginComponentProps) => { - const {component} = props; - +const AppBarPluginComponent = ({ + component, +}: AppBarComponentProps) => { const channel = useSelector(getCurrentChannel); const channelMember = useSelector(getMyCurrentChannelMembership); const activeRhsComponent = useSelector(getActiveRhsComponent); const [imageLoadState, setImageLoadState] = useState(ImageLoadState.LOADING); + const iconUrl = 'iconUrl' in component ? component.iconUrl : undefined; + const icon = 'icon' in component ? component.icon : undefined; + const dropdownText = 'dropdownText' in component ? component.dropdownText : undefined; + const rhsComponentId = 'rhsComponentId' in component ? component.rhsComponentId : undefined; + useEffect(() => { setImageLoadState(ImageLoadState.LOADING); - }, [component.iconUrl]); + }, [iconUrl]); const onImageLoadComplete = () => { setImageLoadState(ImageLoadState.LOADED); @@ -54,9 +59,8 @@ const AppBarPluginComponent = (props: PluginComponentProps) => { }; const buttonId = `app-bar-icon-${component.pluginId}`; - const tooltipText = component.tooltipText || component.dropdownText || component.pluginId; + const tooltipText = component.tooltipText || dropdownText || component.pluginId; - const iconUrl = component.iconUrl; let content: React.ReactNode = (
{
); - const isButtonActive = component.rhsComponentId ? activeRhsComponent?.id === component.rhsComponentId : component.pluginId === activeRhsComponent?.pluginId; + const isButtonActive = rhsComponentId ? activeRhsComponent?.id === rhsComponentId : component.pluginId === activeRhsComponent?.pluginId; if (!iconUrl) { content = ( @@ -81,7 +85,7 @@ const AppBarPluginComponent = (props: PluginComponentProps) => { tabIndex={0} className={classNames('app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered', {'app-bar__old-icon--active': isButtonActive})} > - {component.icon} + {icon}
); } @@ -103,7 +107,13 @@ const AppBarPluginComponent = (props: PluginComponentProps) => { id={buttonId} className={classNames('app-bar__icon', {'app-bar__icon--active': isButtonActive})} onClick={() => { - component.action?.(channel, channelMember); + if (channel && channelMember) { + component.action?.(channel, channelMember); + return; + } + if ('rhsComponentId' in component) { + component.action(); + } }} > {content} diff --git a/webapp/channels/src/components/async_load.tsx b/webapp/channels/src/components/async_load.tsx index e987d4a5a0..da72a5c01e 100644 --- a/webapp/channels/src/components/async_load.tsx +++ b/webapp/channels/src/components/async_load.tsx @@ -1,7 +1,11 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {type ComponentType} from 'react'; +import React, {lazy, type ComponentType} from 'react'; + +import type {PluggableComponentType, PluggableProps} from 'plugins/pluggable/pluggable'; + +import type {PluginsState, ProductSubComponentNames} from 'types/store/plugins'; export function makeAsyncComponent(displayName: string, LazyComponent: React.ComponentType, fallback: React.ReactNode = null) { const Component: ComponentType = (props) => ( @@ -12,3 +16,17 @@ export function makeAsyncComponent(displayName: string, LazyComp Component.displayName = displayName; return Component; } + +export function makeAsyncPluggableComponent() { + const LazyComponent = lazy(() => import('plugins/pluggable')) as PluggableComponentType; + + const Component = (props: PluggableProps) => ( + + {...props}/> + + ); + + Component.displayName = 'Pluggable'; + + return Component; +} diff --git a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown.test.tsx b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown.test.tsx index 39cc94e838..b551064fc0 100644 --- a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown.test.tsx +++ b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown.test.tsx @@ -33,8 +33,8 @@ describe('components/ChannelHeaderDropdown', () => { const props: Props = { ...defaultProps, pluginMenuItems: [ - {id: 'plugin-1', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-1-text'}, - {id: 'plugin-2', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-2-text'}, + {id: 'plugin-1', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-1-text', shouldRender: () => true}, + {id: 'plugin-2', pluginId: 'playbooks', action: jest.fn(), text: 'plugin-2-text', shouldRender: () => true}, ], }; const wrapper = shallow(); diff --git a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx index 75f7b93f26..30e8a0ee88 100644 --- a/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx +++ b/webapp/channels/src/components/channel_header_dropdown/channel_header_dropdown_items.tsx @@ -30,7 +30,7 @@ import MobileChannelHeaderPlug from 'plugins/mobile_channel_header_plug'; import {Constants, ModalIdentifiers} from 'utils/constants'; import {localizeMessage} from 'utils/utils'; -import type {PluginComponent} from 'types/store/plugins'; +import type {ChannelHeaderAction} from 'types/store/plugins'; import MenuItemCloseChannel from './menu_items/close_channel'; import MenuItemCloseMessage from './menu_items/close_message'; @@ -51,7 +51,7 @@ export type Props = { isArchived: boolean; isMobile: boolean; penultimateViewedChannelName: string; - pluginMenuItems: PluginComponent[]; + pluginMenuItems: ChannelHeaderAction[]; isLicensedForLDAPGroups: boolean; isChannelBookmarksEnabled: boolean; } diff --git a/webapp/channels/src/components/file_preview_modal/index.ts b/webapp/channels/src/components/file_preview_modal/index.ts index bc78bee97d..bbb5a95876 100644 --- a/webapp/channels/src/components/file_preview_modal/index.ts +++ b/webapp/channels/src/components/file_preview_modal/index.ts @@ -16,7 +16,6 @@ import {makeAsyncComponent} from 'components/async_load'; import {canDownloadFiles} from 'utils/file_utils'; import type {GlobalState} from 'types/store'; -import type {FilePreviewComponent} from 'types/store/plugins'; import type {Props} from './file_preview_modal'; @@ -34,7 +33,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) { canDownloadFiles: canDownloadFiles(config), enablePublicLink: config.EnablePublicLink === 'true', isMobileView: getIsMobileView(state), - pluginFilePreviewComponents: state.plugins.components.FilePreview as unknown as FilePreviewComponent[], + pluginFilePreviewComponents: state.plugins.components.FilePreview, post: ownProps.post || getPost(state, ownProps.postId || ''), }; } diff --git a/webapp/channels/src/components/file_search_results/index.tsx b/webapp/channels/src/components/file_search_results/index.tsx index c17dddc5e4..8e6f4b50a5 100644 --- a/webapp/channels/src/components/file_search_results/index.tsx +++ b/webapp/channels/src/components/file_search_results/index.tsx @@ -13,7 +13,7 @@ import {getChannel} from 'mattermost-redux/selectors/entities/channels'; import {openModal} from 'actions/views/modals'; import type {GlobalState} from 'types/store'; -import type {FileDropdownPluginComponent} from 'types/store/plugins'; +import type {FilesDropdownAction} from 'types/store/plugins'; import FileSearchResultItem from './file_search_result_item'; @@ -21,7 +21,7 @@ export type OwnProps = { channelId: string; fileInfo: FileInfo; teamName: string; - pluginMenuItems?: FileDropdownPluginComponent[]; + pluginMenuItems?: FilesDropdownAction[]; }; function mapStateToProps(state: GlobalState, ownProps: OwnProps) { diff --git a/webapp/channels/src/components/file_upload/file_upload.tsx b/webapp/channels/src/components/file_upload/file_upload.tsx index 6257710f66..ce5c2364a0 100644 --- a/webapp/channels/src/components/file_upload/file_upload.tsx +++ b/webapp/channels/src/components/file_upload/file_upload.tsx @@ -42,7 +42,7 @@ import { isTextDroppableEvent, } from 'utils/utils'; -import type {FilesWillUploadHook, PluginComponent} from 'types/store/plugins'; +import type {FilesWillUploadHook, FileUploadMethodAction} from 'types/store/plugins'; const holders = defineMessages({ limited: { @@ -147,7 +147,7 @@ export type Props = { /** * Plugin file upload methods to be added */ - pluginFileUploadMethods: PluginComponent[]; + pluginFileUploadMethods: FileUploadMethodAction[]; pluginFilesWillUploadHooks: FilesWillUploadHook[]; /** diff --git a/webapp/channels/src/components/file_upload/index.ts b/webapp/channels/src/components/file_upload/index.ts index 7251cab965..3e8934ebbb 100644 --- a/webapp/channels/src/components/file_upload/index.ts +++ b/webapp/channels/src/components/file_upload/index.ts @@ -14,7 +14,6 @@ import {getEditingPostDetailsAndPost} from 'selectors/posts'; import {canUploadFiles} from 'utils/file_utils'; import type {GlobalState} from 'types/store'; -import type {FilesWillUploadHook} from 'types/store/plugins'; import FileUpload from './file_upload'; @@ -31,7 +30,7 @@ function mapStateToProps(state: GlobalState) { canUploadFiles: canUploadFiles(config), locale: getCurrentLocale(state), pluginFileUploadMethods: state.plugins.components.FileUploadMethod, - pluginFilesWillUploadHooks: state.plugins.components.FilesWillUploadHook as unknown as FilesWillUploadHook[], + pluginFilesWillUploadHooks: state.plugins.components.FilesWillUploadHook, centerChannelPostBeingEdited, rhsPostBeingEdited, }; diff --git a/webapp/channels/src/components/main_menu/__snapshots__/main_menu.test.tsx.snap b/webapp/channels/src/components/main_menu/__snapshots__/main_menu.test.tsx.snap index b76dd22b68..e305167eab 100644 --- a/webapp/channels/src/components/main_menu/__snapshots__/main_menu.test.tsx.snap +++ b/webapp/channels/src/components/main_menu/__snapshots__/main_menu.test.tsx.snap @@ -1899,6 +1899,7 @@ exports[`components/Menu should match snapshot with plugins 1`] = ` key="plugin-id-1_pluginmenuitem" onClick={[Function]} show={true} + text="some text" /> @@ -2249,16 +2251,28 @@ exports[`components/Menu should match snapshot with plugins in mobile 1`] = ` + } id="plugin-id-1_pluginmenuitem" key="plugin-id-1_pluginmenuitem" onClick={[Function]} show={true} + text="some text" /> + } id="plugind-id-2_pluginmenuitem" key="plugind-id-2_pluginmenuitem" onClick={[Function]} show={true} + text="some text" /> diff --git a/webapp/channels/src/components/main_menu/main_menu.test.tsx b/webapp/channels/src/components/main_menu/main_menu.test.tsx index 60f700af16..395a0556ae 100644 --- a/webapp/channels/src/components/main_menu/main_menu.test.tsx +++ b/webapp/channels/src/components/main_menu/main_menu.test.tsx @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {shallow} from 'enzyme'; +import type {ComponentProps} from 'react'; import React from 'react'; import {createIntl} from 'react-intl'; import {Provider} from 'react-redux'; @@ -12,7 +13,6 @@ import Menu from 'components/widgets/menu/menu'; import {mountWithIntl} from 'tests/helpers/intl-test-helper'; import mockStore from 'tests/test_store'; -import {Constants} from 'utils/constants'; import {TestHelper} from 'utils/test_helper'; import {MainMenu} from './main_menu'; @@ -28,24 +28,18 @@ describe('components/Menu', () => { // return wrapper.find('MainMenu').shallow(); }; - const defaultProps = { + const defaultProps: ComponentProps = { mobile: false, teamId: 'team-id', - teamType: Constants.OPEN_TEAM, teamName: 'team_name', currentUser: TestHelper.getUserMock(), appDownloadLink: undefined, enableCommands: false, - enableCustomEmoji: false, enableIncomingWebhooks: false, enableOAuthServiceProvider: false, enableOutgoingWebhooks: false, canManageSystemBots: false, - canCreateOrDeleteCustomEmoji: false, canManageIntegrations: true, - enableUserCreation: false, - enableEmailInvitations: false, - enablePluginMarketplace: false, experimentalPrimaryTeam: undefined, helpLink: undefined, reportAProblemLink: undefined, @@ -61,13 +55,10 @@ describe('components/Menu', () => { showFlaggedPosts: jest.fn(), closeRightHandSide: jest.fn(), closeRhsMenu: jest.fn(), - getCloudLimits: jest.fn(), }, teamIsGroupConstrained: false, isCloud: false, isStarterFree: false, - subscription: {}, - userIsAdmin: true, isFreeTrial: false, usageDeltaTeams: 1, }; @@ -178,23 +169,21 @@ describe('components/Menu', () => { }); test('should match snapshot with plugins', () => { - const props = { + const props: ComponentProps = { ...defaultProps, pluginMenuItems: [{ id: 'plugin-id-1', pluginId: 'plugin-1', mobileIcon: , action: jest.fn, - dropdownText: 'some dropdown text', - tooltipText: 'some tooltip text', + text: 'some text', }, { id: 'plugind-id-2', pluginId: 'plugin-2', mobileIcon: , action: jest.fn, - dropdownText: 'some dropdown text', - tooltipText: 'some tooltip text', + text: 'some text', }, ], }; @@ -203,24 +192,22 @@ describe('components/Menu', () => { }); test('should match snapshot with plugins in mobile', () => { - const props = { + const props: ComponentProps = { ...defaultProps, mobile: true, pluginMenuItems: [{ id: 'plugin-id-1', pluginId: 'plugin-1', - icon: , + mobileIcon: , action: jest.fn, - dropdownText: 'some dropdown text', - tooltipText: 'some tooltip text', + text: 'some text', }, { id: 'plugind-id-2', pluginId: 'plugin-2', - icon: , + mobileIcon: , action: jest.fn, - dropdownText: 'some dropdown text', - tooltipText: 'some tooltip text', + text: 'some text', }, ], }; diff --git a/webapp/channels/src/components/main_menu/main_menu.tsx b/webapp/channels/src/components/main_menu/main_menu.tsx index 86973fa61c..fceb70043b 100644 --- a/webapp/channels/src/components/main_menu/main_menu.tsx +++ b/webapp/channels/src/components/main_menu/main_menu.tsx @@ -33,7 +33,7 @@ import {makeUrlSafe} from 'utils/url'; import * as UserAgent from 'utils/user_agent'; import type {ModalData} from 'types/actions'; -import type {PluginComponent} from 'types/store/plugins'; +import type {MainMenuAction} from 'types/store/plugins'; import LearnAboutTeamsLink from './learn_about_teams_link'; import './main_menu.scss'; @@ -56,7 +56,7 @@ export type Props = { helpLink?: string; reportAProblemLink?: string; moreTeamsToJoin: boolean; - pluginMenuItems?: PluginComponent[]; + pluginMenuItems?: MainMenuAction[]; isMentionSearch?: boolean; teamIsGroupConstrained: boolean; isLicensedForLDAPGroups?: boolean; diff --git a/webapp/channels/src/components/new_channel_modal/new_channel_modal.tsx b/webapp/channels/src/components/new_channel_modal/new_channel_modal.tsx index 105bac64bd..10f2a4ca2d 100644 --- a/webapp/channels/src/components/new_channel_modal/new_channel_modal.tsx +++ b/webapp/channels/src/components/new_channel_modal/new_channel_modal.tsx @@ -74,8 +74,7 @@ const NewChannelModal = () => { const [channelInputError, setChannelInputError] = useState(false); // create a board along with the channel - const pluginsComponentsList = useSelector((state: GlobalState) => state.plugins.components); - const createBoardFromChannelPlugin = pluginsComponentsList?.CreateBoardFromTemplate; + const createBoardFromChannelPlugin = useSelector((state: GlobalState) => state.plugins.components.CreateBoardFromTemplate); const newChannelWithBoardPulsatingDotState = useSelector((state: GlobalState) => getPreference(state, Preferences.APP_BAR, Preferences.NEW_CHANNEL_WITH_BOARD_TOUR_SHOWED, '')); const [canCreateFromPluggable, setCanCreateFromPluggable] = useState(true); diff --git a/webapp/channels/src/components/new_search/search_box_hints.tsx b/webapp/channels/src/components/new_search/search_box_hints.tsx index fedc8fe258..60bfce3d30 100644 --- a/webapp/channels/src/components/new_search/search_box_hints.tsx +++ b/webapp/channels/src/components/new_search/search_box_hints.tsx @@ -64,7 +64,7 @@ const SearchBoxHints = ({searchTerms, setSearchTerms, searchType, providerResult return null; } - const Component: any = pluginComponentInfo.component; + const Component = pluginComponentInfo.component; return ( diff --git a/webapp/channels/src/components/new_search/search_box_suggestions.tsx b/webapp/channels/src/components/new_search/search_box_suggestions.tsx index 1c03e29fd2..a5a1fe01de 100644 --- a/webapp/channels/src/components/new_search/search_box_suggestions.tsx +++ b/webapp/channels/src/components/new_search/search_box_suggestions.tsx @@ -113,7 +113,7 @@ const SearchSuggestions = ({searchType, searchTerms, suggestionsHeader, provider ); } - const pluginComponentInfo = searchPluginSuggestions.find(({pluginId}: any) => { + const pluginComponentInfo = searchPluginSuggestions.find(({pluginId}) => { if (searchType === pluginId) { return true; } @@ -124,7 +124,7 @@ const SearchSuggestions = ({searchType, searchTerms, suggestionsHeader, provider return null; } - const Component: any = pluginComponentInfo.component; + const Component = pluginComponentInfo.component; return ( diff --git a/webapp/channels/src/components/plugin_link_tooltip/index.tsx b/webapp/channels/src/components/plugin_link_tooltip/index.tsx index 92396274d6..af18869dbb 100644 --- a/webapp/channels/src/components/plugin_link_tooltip/index.tsx +++ b/webapp/channels/src/components/plugin_link_tooltip/index.tsx @@ -81,7 +81,7 @@ export default function PluginLinkTooltip(props: Props) { {...getFloatingProps()} > diff --git a/webapp/channels/src/components/post/post_component.tsx b/webapp/channels/src/components/post/post_component.tsx index 6f81e7497f..54e740cca9 100644 --- a/webapp/channels/src/components/post/post_component.tsx +++ b/webapp/channels/src/components/post/post_component.tsx @@ -47,7 +47,7 @@ import {isKeyPressed} from 'utils/keyboard'; import * as PostUtils from 'utils/post_utils'; import {getDateForUnixTicks, makeIsEligibleForClick} from 'utils/utils'; -import type {PostPluginComponent, PluginComponent} from 'types/store/plugins'; +import type {PostActionComponent, PostPluginComponent} from 'types/store/plugins'; import PostOptions from './post_options'; import PostUserProfile from './user_profile'; @@ -116,7 +116,7 @@ export type Props = { isPostPriorityEnabled: boolean; isCardOpen?: boolean; canDelete?: boolean; - pluginActions: PluginComponent[]; + pluginActions: PostActionComponent[]; }; const PostComponent = (props: Props): JSX.Element => { diff --git a/webapp/channels/src/components/post/post_options.tsx b/webapp/channels/src/components/post/post_options.tsx index 25eec3bfc3..86b56a13af 100644 --- a/webapp/channels/src/components/post/post_options.tsx +++ b/webapp/channels/src/components/post/post_options.tsx @@ -22,7 +22,7 @@ import PostRecentReactions from 'components/post_view/post_recent_reactions'; import {Locations, Constants} from 'utils/constants'; import {isSystemMessage, fromAutoResponder} from 'utils/post_utils'; -import type {PluginComponent} from 'types/store/plugins'; +import type {PostActionComponent} from 'types/store/plugins'; type Props = { post: Post; @@ -52,7 +52,7 @@ type Props = { isPostHeaderVisible?: boolean | null; isPostBeingEdited?: boolean; canDelete?: boolean; - pluginActions: PluginComponent[]; + pluginActions: PostActionComponent[]; actions: { emitShortcutReactToLastPostFrom: (emittedFrom: 'CENTER' | 'RHS_ROOT' | 'NO_WHERE') => void; }; @@ -191,7 +191,7 @@ const PostOptions = (props: Props): JSX.Element => { pluginItems = props.pluginActions?. map((item) => { if (item.component) { - const Component = item.component as any; + const Component = item.component; return ( { - const baseProps = { + const baseProps: ComponentProps = { imageProps: {} as Record, pluginHooks: [], message: 'message', @@ -211,7 +210,7 @@ describe('components/PostMarkdown', () => { }); test('plugin hooks can build upon other hook message updates', () => { - const props = { + const props: ComponentProps = { ...baseProps, message: 'world', post: TestHelper.getPostMock({ @@ -226,16 +225,20 @@ describe('components/PostMarkdown', () => { }), pluginHooks: [ { + id: 'some id', + pluginId: 'some plugin', hook: (post: Post, updatedMessage: string) => { return 'hello ' + updatedMessage; }, }, { + id: 'different id', + pluginId: 'different plugin', hook: (post: Post, updatedMessage: string) => { return updatedMessage + '!'; }, }, - ] as MessageWillFormatHook[], + ], }; renderWithContext(, state); expect(screen.queryByText('world', {exact: true})).not.toBeInTheDocument(); @@ -245,7 +248,7 @@ describe('components/PostMarkdown', () => { }); test('plugin hooks can overwrite other hooks messages', () => { - const props = { + const props: ComponentProps = { ...baseProps, message: 'world', post: TestHelper.getPostMock({ @@ -260,16 +263,20 @@ describe('components/PostMarkdown', () => { }), pluginHooks: [ { + id: 'some id', + pluginId: 'some plugin', hook: (post: Post) => { return 'hello ' + post.message; }, }, { + id: 'different id', + pluginId: 'different plugin', hook: (post: Post) => { return post.message + '!'; }, }, - ] as unknown as MessageWillFormatHook[], + ], }; renderWithContext(, state); expect(screen.queryByText('world', {exact: true})).not.toBeInTheDocument(); diff --git a/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx b/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx index 7ccd662cf5..62f087d58e 100644 --- a/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx +++ b/webapp/channels/src/components/post_view/channel_intro_message/pluggable_intro_buttons/pluggable_intro_buttons.tsx @@ -5,26 +5,30 @@ import React from 'react'; import type {Channel, ChannelMembership} from '@mattermost/types/channels'; -import type {PluginComponent} from 'types/store/plugins'; +import type {ChannelIntroButtonAction} from 'types/store/plugins'; type Props = { channel: Channel; channelMember?: ChannelMembership; - pluginButtons: PluginComponent[]; + pluginButtons: ChannelIntroButtonAction[]; } -const PluggableIntroButtons = React.memo((props: Props) => { - const channelIsArchived = props.channel.delete_at !== 0; - if (channelIsArchived || props.pluginButtons.length === 0) { +const PluggableIntroButtons = React.memo(({ + channel, + pluginButtons, + channelMember, +}: Props) => { + const channelIsArchived = channel.delete_at !== 0; + if (channelIsArchived || pluginButtons.length === 0 || !channelMember) { return null; } - const buttons = props.pluginButtons.map((buttonProps) => { + const buttons = pluginButtons.map((buttonProps) => { return (