diff --git a/server/channels/app/notify_admin.go b/server/channels/app/notify_admin.go index ab0bc71719..21104e4047 100644 --- a/server/channels/app/notify_admin.go +++ b/server/channels/app/notify_admin.go @@ -117,7 +117,7 @@ func (a *App) SendNotifyAdminPosts(c request.CTX, workspaceName string, currentS return nil } - userBasedPaidFeatureData, userBasedPluginData := a.groupNotifyAdminByUser(data) + userBasedPaidFeatureData := a.groupNotifyAdminByUser(data) featureBasedData := a.groupNotifyAdminByPaidFeature(data) pluginBasedData := a.groupNotifyAdminByPlugin(data) @@ -125,41 +125,12 @@ func (a *App) SendNotifyAdminPosts(c request.CTX, workspaceName string, currentS if len(userBasedPaidFeatureData) > 0 && len(featureBasedData) > 0 { a.upgradePlanAdminNotifyPost(c, workspaceName, userBasedPaidFeatureData, featureBasedData, systemBot, admin, trial) } - - if len(userBasedPluginData) > 0 { - a.pluginInstallAdminNotifyPost(c, userBasedPluginData, pluginBasedData, systemBot, admin) - } } a.FinishSendAdminNotifyPost(c, trial, now, pluginBasedData) return nil } -func (a *App) pluginInstallAdminNotifyPost(c request.CTX, userBasedData map[string][]*model.NotifyAdminData, pluginBasedPluginData map[string][]*model.NotifyAdminData, systemBot *model.Bot, admin *model.User) { - props := make(model.StringInterface) - - channel, appErr := a.GetOrCreateDirectChannel(c, systemBot.UserId, admin.Id) - if appErr != nil { - c.Logger().Warn("Error getting direct channel", mlog.Err(appErr)) - return - } - - post := &model.Post{ - UserId: systemBot.UserId, - ChannelId: channel.Id, - Type: fmt.Sprintf("%spl_notification", model.PostCustomTypePrefix), // webapp will have to create renderer for this custom post type - } - - props["requested_plugins_by_plugin_ids"] = pluginBasedPluginData - props["requested_plugins_by_user_ids"] = userBasedData - post.SetProps(props) - - _, appErr = a.CreatePost(c, post, channel, model.CreatePostFlags{SetOnline: true}) - if appErr != nil { - c.Logger().Warn("Error creating post", mlog.Err(appErr)) - } -} - func (a *App) upgradePlanAdminNotifyPost(c request.CTX, workspaceName string, userBasedData map[string][]*model.NotifyAdminData, featureBasedData map[model.MattermostFeature][]*model.NotifyAdminData, systemBot *model.Bot, admin *model.User, trial bool) { props := make(model.StringInterface) T := i18n.GetUserTranslations(admin.Locale) @@ -276,17 +247,12 @@ func (a *App) FinishSendAdminNotifyPost(rctx request.CTX, trial bool, now int64, } } -func (a *App) groupNotifyAdminByUser(data []*model.NotifyAdminData) (map[string][]*model.NotifyAdminData, map[string][]*model.NotifyAdminData) { +func (a *App) groupNotifyAdminByUser(data []*model.NotifyAdminData) map[string][]*model.NotifyAdminData { userBasedPaidFeatureData := make(map[string][]*model.NotifyAdminData) - userBasedPluginData := make(map[string][]*model.NotifyAdminData) for _, d := range data { - if strings.HasPrefix(string(d.RequiredFeature), string(model.PluginFeature)) { - userBasedPluginData[d.UserId] = append(userBasedPluginData[d.UserId], d) - } else { - userBasedPaidFeatureData[d.UserId] = append(userBasedPaidFeatureData[d.UserId], d) - } + userBasedPaidFeatureData[d.UserId] = append(userBasedPaidFeatureData[d.UserId], d) } - return userBasedPaidFeatureData, userBasedPluginData + return userBasedPaidFeatureData } func (a *App) groupNotifyAdminByPaidFeature(data []*model.NotifyAdminData) map[model.MattermostFeature][]*model.NotifyAdminData { diff --git a/server/channels/app/notify_admin_test.go b/server/channels/app/notify_admin_test.go index fd15d2b936..7ab3f13300 100644 --- a/server/channels/app/notify_admin_test.go +++ b/server/channels/app/notify_admin_test.go @@ -14,8 +14,6 @@ import ( "github.com/mattermost/mattermost/server/public/model" ) -const PluginIDGithub = "github" - func Test_SendNotifyAdminPosts(t *testing.T) { t.Run("no error sending non trial upgrade post when no notifications are available", func(t *testing.T) { th := Setup(t).InitBasic() @@ -137,119 +135,6 @@ func Test_SendNotifyAdminPosts(t *testing.T) { require.Equal(t, "1 member of the test workspace has requested starting the Enterprise trial for access to: ", post.Message) }) - t.Run("successfully send install plugin notification", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - // some notifications - _, appErr := th.App.SaveAdminNotifyData(&model.NotifyAdminData{ - UserId: th.BasicUser.Id, - RequiredPlan: PluginIDGithub, - RequiredFeature: model.PluginFeature, - Trial: false, - }) - require.Nil(t, appErr) - - appErr = th.App.SendNotifyAdminPosts(th.Context, "", "", false) - require.Nil(t, appErr) - - bot, appErr := th.App.GetSystemBot(th.Context) - require.Nil(t, appErr) - - var channel *model.Channel - var err error - var timeout = 5 * time.Second - begin := time.Now() - - for { - if time.Since(begin) > timeout { - break - } - channel, err = th.App.Srv().Store().Channel().GetByName("", model.GetDMNameFromIds(bot.UserId, th.SystemAdminUser.Id), false) - if err == nil && channel != nil { - break - } - time.Sleep(100 * time.Millisecond) - } - require.NoError(t, err, "Expected message to have been sent within %d seconds", timeout) - postList, err := th.App.Srv().Store().Post().GetPosts(model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 1}, false, map[string]bool{}) - require.NoError(t, err) - - post := postList.Posts[postList.Order[0]] - - require.Equal(t, fmt.Sprintf("%spl_notification", model.PostCustomTypePrefix), post.Type) - require.Equal(t, bot.UserId, post.UserId) - }) - - t.Run("persist notify admin data after sending the install plugin notification", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - // some notifications - _, appErr := th.App.SaveAdminNotifyData(&model.NotifyAdminData{ - UserId: th.BasicUser.Id, - RequiredPlan: PluginIDGithub, - RequiredFeature: model.PluginFeature, - Trial: false, - }) - require.Nil(t, appErr) - - appErr = th.App.SendNotifyAdminPosts(th.Context, "", "", false) - require.Nil(t, appErr) - - bot, appErr := th.App.GetSystemBot(th.Context) - require.Nil(t, appErr) - - var channel *model.Channel - var err error - var timeout = 5 * time.Second - begin := time.Now() - - for { - if time.Since(begin) > timeout { - break - } - channel, err = th.App.Srv().Store().Channel().GetByName("", model.GetDMNameFromIds(bot.UserId, th.SystemAdminUser.Id), false) - if err == nil && channel != nil { - break - } - time.Sleep(100 * time.Millisecond) - } - require.NoError(t, err, "Expected message to have been sent within %d seconds", timeout) - postList, err := th.App.Srv().Store().Post().GetPosts(model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 1}, false, map[string]bool{}) - require.NoError(t, err) - - post := postList.Posts[postList.Order[0]] - - require.Equal(t, fmt.Sprintf("%spl_notification", model.PostCustomTypePrefix), post.Type) - require.Equal(t, bot.UserId, post.UserId) - - data, err := th.App.Srv().Store().NotifyAdmin().GetDataByUserIdAndFeature(th.BasicUser.Id, model.PluginFeature) - require.NoError(t, err) - require.Equal(t, len(data), 1) - }) - - t.Run("error sending more than one notification to the same user and for the same plugin", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - err := th.App.SaveAdminNotification(th.BasicUser.Id, &model.NotifyAdminToUpgradeRequest{ - RequiredPlan: PluginIDGithub, - RequiredFeature: model.PluginFeature, - TrialNotification: false, - }) - - require.Nil(t, err) - - err = th.App.SaveAdminNotification(th.BasicUser.Id, &model.NotifyAdminToUpgradeRequest{ - RequiredPlan: PluginIDGithub, - RequiredFeature: model.PluginFeature, - TrialNotification: false, - }) - - require.Equal(t, err.Error(), "app.SaveAdminNotification: Already notified admin") - }) - t.Run("error when trying to send upgrade post before end of cool off period", func(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() @@ -370,67 +255,4 @@ func Test_SendNotifyAdminPosts(t *testing.T) { require.Equal(t, bot.UserId, post.UserId) require.Equal(t, "1 member of the test workspace has requested a workspace upgrade for: ", post.Message) // expect only one member's notification even though 2 were added }) - - t.Run("correctly send upgrade and install plugin post with the correct user request", func(t *testing.T) { - th := Setup(t).InitBasic() - defer th.TearDown() - - os.Setenv("MM_NOTIFY_ADMIN_COOL_OFF_DAYS", "0") - defer os.Unsetenv("MM_NOTIFY_ADMIN_COOL_OFF_DAYS") - - th.App.Srv().SetLicense(model.NewTestLicense("cloud")) - - // some notifications - _, appErr := th.App.SaveAdminNotifyData(&model.NotifyAdminData{ - UserId: th.BasicUser.Id, - RequiredPlan: model.LicenseShortSkuProfessional, - RequiredFeature: model.PaidFeatureGuestAccounts, - Trial: false, - }) - require.Nil(t, appErr) - appErr = th.App.SendNotifyAdminPosts(th.Context, "test", "", false) - require.Nil(t, appErr) - - // some notifications - _, appErr = th.App.SaveAdminNotifyData(&model.NotifyAdminData{ - UserId: th.BasicUser.Id, - RequiredPlan: PluginIDGithub, - RequiredFeature: model.PluginFeature, - Trial: false, - }) - require.Nil(t, appErr) - appErr = th.App.SendNotifyAdminPosts(th.Context, "test", "", false) - require.Nil(t, appErr) - - bot, appErr := th.App.GetSystemBot(th.Context) - require.Nil(t, appErr) - - var channel *model.Channel - var err error - var timeout = 5 * time.Second - begin := time.Now() - - for { - if time.Since(begin) > timeout { - break - } - channel, err = th.App.Srv().Store().Channel().GetByName("", model.GetDMNameFromIds(bot.UserId, th.SystemAdminUser.Id), false) - if err == nil && channel != nil { - break - } - time.Sleep(100 * time.Millisecond) - } - require.NoError(t, err, "Expected message to have been sent within %d seconds", timeout) - postList, err := th.App.Srv().Store().Post().GetPosts(model.GetPostsOptions{ChannelId: channel.Id, Page: 0, PerPage: 2}, false, map[string]bool{}) - require.NoError(t, err) - - installPluginPost := postList.Posts[postList.Order[0]] - require.Equal(t, fmt.Sprintf("%spl_notification", model.PostCustomTypePrefix), installPluginPost.Type) - require.Equal(t, bot.UserId, installPluginPost.UserId) - - upgradePost := postList.Posts[postList.Order[1]] - require.Equal(t, fmt.Sprintf("%sup_notification", model.PostCustomTypePrefix), upgradePost.Type) - require.Equal(t, bot.UserId, upgradePost.UserId) - require.Equal(t, "1 member of the test workspace has requested a workspace upgrade for: ", upgradePost.Message) - }) } diff --git a/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.test.tsx b/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.test.tsx deleted file mode 100644 index 5cb714ee9a..0000000000 --- a/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import type {DeepPartial} from '@mattermost/types/utilities'; - -import {isCustomPostProps, type CustomPostProps} from '.'; - -describe('isCustomPostProps', () => { - it('no content', () => { - const props: CustomPostProps = { - requested_plugins_by_plugin_ids: {}, - requested_plugins_by_user_ids: {}, - }; - expect(isCustomPostProps(props)).toBe(true); - }); - - it('content but no elements', () => { - const props: CustomPostProps = { - requested_plugins_by_plugin_ids: {'some id': []}, - requested_plugins_by_user_ids: {'some id': []}, - }; - expect(isCustomPostProps(props)).toBe(true); - }); - - it('content with elements', () => { - const props: CustomPostProps = { - requested_plugins_by_plugin_ids: {'some id': [{ - user_id: '123', - }]}, - requested_plugins_by_user_ids: {'some id': [{ - user_id: '123', - }]}, - }; - expect(isCustomPostProps(props)).toBe(true); - }); - - it('all values are required', () => { - const baseProp: CustomPostProps = { - requested_plugins_by_plugin_ids: {}, - requested_plugins_by_user_ids: {}, - }; - - expect(isCustomPostProps(baseProp)).toBe(true); - - for (const key of Object.keys(baseProp)) { - const wrongProp: Partial = {...baseProp}; - delete wrongProp[key as keyof CustomPostProps]; - expect(isCustomPostProps(wrongProp)).toBe(false); - } - - const wrongProp: DeepPartial = { - requested_plugins_by_plugin_ids: {'some id': [{}]}, - requested_plugins_by_user_ids: {'some id': []}, - }; - expect(isCustomPostProps(wrongProp)).toBe(false); - }); - - it('common false cases', () => { - expect(isCustomPostProps('')).toBe(false); - expect(isCustomPostProps(undefined)).toBe(false); - expect(isCustomPostProps(true)).toBe(false); - expect(isCustomPostProps(1)).toBe(false); - expect(isCustomPostProps([])).toBe(false); - }); -}); diff --git a/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.tsx b/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.tsx deleted file mode 100644 index 8f534c8cf2..0000000000 --- a/webapp/channels/src/components/custom_open_plugin_install_post_renderer/index.tsx +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import uniqWith from 'lodash/uniqWith'; -import React, {useEffect, useMemo} from 'react'; -import {FormattedMessage, useIntl} from 'react-intl'; -import {useSelector, useDispatch} from 'react-redux'; -import {Link} from 'react-router-dom'; - -import type {MarketplacePlugin} from '@mattermost/types/marketplace'; -import type {Post} from '@mattermost/types/posts'; -import {isArrayOf, isRecordOf} from '@mattermost/types/utilities'; - -import {getMissingProfilesByIds} from 'mattermost-redux/actions/users'; -import {getUsers} from 'mattermost-redux/selectors/entities/users'; - -import {fetchListing, installPlugin} from 'actions/marketplace'; -import {getError, getInstalledListing, getInstalling, getPlugins} from 'selectors/views/marketplace'; - -import Markdown from 'components/markdown'; -import MarketplaceModal from 'components/plugin_marketplace/marketplace_modal'; -import ToggleModalButton from 'components/toggle_modal_button'; - -import {ModalIdentifiers} from 'utils/constants'; - -import type {GlobalState} from 'types/store'; - -// We only define the props used in this component for -// clarity. If more props are needed in the future, -// feel free to add them. -type PluginRequest = { - user_id: string; -} - -function isPluginRequest(v: unknown): v is PluginRequest { - if (typeof v !== 'object' || v === null) { - return false; - } - - const request = v as PluginRequest; - - if (typeof request.user_id !== 'string') { - return false; - } - - return true; -} - -type RequestedPlugins = Record - -export type CustomPostProps = { - requested_plugins_by_plugin_ids: RequestedPlugins; - requested_plugins_by_user_ids: RequestedPlugins; -} - -export function isCustomPostProps(v: unknown): v is CustomPostProps { - if (typeof v !== 'object' || !v) { - return false; - } - - const props = v as CustomPostProps; - - if (!isRecordOf(props.requested_plugins_by_plugin_ids, (e) => isArrayOf(e, isPluginRequest))) { - return false; - } - - if (!isRecordOf(props.requested_plugins_by_user_ids, (e) => isArrayOf(e, isPluginRequest))) { - return false; - } - - return true; -} - -const usersListStyle = { - margin: '20px 0', -}; - -const InstallLink = (props: {pluginId: string; pluginName: string}) => { - const dispatch = useDispatch(); - - return ( - dispatch(installPlugin(props.pluginId))} - style={{color: 'var(--denim-button-bg)', fontWeight: '600'}} - > - - - ); -}; - -const ConfigureLink = (props: {pluginId: string; pluginName: string}) => { - return ( - <> - - {' '} - - - - - ); -}; - -const InstallAndConfigureLink = (props: {pluginId: string; pluginName: string}) => { - const installedListing = useSelector(getInstalledListing) as MarketplacePlugin[]; - const error = useSelector((state: GlobalState) => getError(state, props.pluginId)); - - const isInstalled = installedListing.some((plugin) => plugin.manifest.id === props.pluginId); - const installing = useSelector((state: GlobalState) => getInstalling(state, props.pluginId)); - if (installing) { - return ( - - - - ); - } else if (!isInstalled && !error) { - return ( - ); - } else if (isInstalled && !error) { - return ( - ); - } - return null; -}; - -export default function OpenPluginInstallPost(props: {post: Post}) { - const customMessageBody = []; - - const dispatch = useDispatch(); - const {formatMessage, formatList} = useIntl(); - - const postProps = isCustomPostProps(props.post.props) ? props.post.props : undefined; - const requestedPluginsByPluginIds = postProps?.requested_plugins_by_plugin_ids; - const requestedPluginsByUserIds = postProps?.requested_plugins_by_user_ids; - - const userProfiles = useSelector(getUsers); - const marketplacePlugins: MarketplacePlugin[] = useSelector(getPlugins); - const marketplacePluginsNamesById = useMemo(() => { - return marketplacePlugins.reduce>((acc, v) => { - acc[v.manifest.id] = v.manifest.name; - return acc; - }, {}); - }, [marketplacePlugins]); - - const getUserIdsForUsersThatRequestedFeature = (requests: PluginRequest[]): string[] => requests.map((request: PluginRequest) => request.user_id); - - useEffect(() => { - if (!marketplacePlugins.length) { - dispatch(fetchListing()); - } - }, [dispatch, marketplacePlugins.length]); - - useEffect(() => { - // process the plugins once the marketplace plugins are fetched and the plugins are available from the props - if (requestedPluginsByPluginIds && marketplacePlugins.length) { - for (const pluginId of Object.keys(requestedPluginsByPluginIds)) { - dispatch(getMissingProfilesByIds(getUserIdsForUsersThatRequestedFeature(requestedPluginsByPluginIds[pluginId]))); - } - } - }, [dispatch, marketplacePlugins, requestedPluginsByPluginIds]); - - const createUsernameMessage = (requests: PluginRequest[]) => { - if (requests.length >= 5) { - return formatMessage({ - id: 'postypes.custom_open_pricing_modal_post_renderer.members', - defaultMessage: '{members} members', - }, {members: requests.length}); - } - - let usernameMessage; - const users = getUserNamesForUsersThatRequestedFeature(requests); - - if (users.length === 1) { - usernameMessage = users[0]; - } else { - const lastUser = users.splice(-1, 1)[0]; - users.push(formatMessage({id: 'postypes.custom_open_pricing_modal_post_renderer.and', defaultMessage: 'and'}) + ' ' + lastUser); - usernameMessage = users.join(', ').replace(/,([^,]*)$/, '$1'); - } - - return usernameMessage; - }; - const getUserNamesForUsersThatRequestedFeature = (requests: PluginRequest[]): string[] => { - const userNames = requests.map((req: PluginRequest) => { - return getUserNameForUser(req.user_id); - }); - return userNames; - }; - - const getUserNameForUser = (userId: string) => { - const unknownName = formatMessage({id: 'postypes.custom_open_pricing_modal_post_renderer.unknown', defaultMessage: '@unknown'}); - const username = userProfiles[userId]?.username; - return username ? '@' + username : unknownName; - }; - - const markDownOptions = { - atSumOfMembersMentions: true, - atPlanMentions: true, - markdown: false, - }; - const pluginIds = Object.keys(requestedPluginsByPluginIds || {}); - if (pluginIds.length && requestedPluginsByUserIds && requestedPluginsByPluginIds) { - let post; - const messageBuilder: string[] = []; - const userIds = Object.keys(requestedPluginsByUserIds); - if (userIds.length === 1 && pluginIds.length === 1) { - const pluginName = marketplacePluginsNamesById[pluginIds[0]]; - messageBuilder.push('@' + userProfiles[userIds[0]]?.username); - messageBuilder.push(' ' + formatMessage({id: 'postypes.custom_open_plugin_install_post_rendered.plugin_request', defaultMessage: 'requested installing the {pluginRequests} app.'}, {pluginRequests: pluginName})); - - const instructions = ( - ( - - {text} - - ), - pluginApp: () => ( - - ), - }} - />); - - const message = formatList(messageBuilder, {style: 'narrow', type: 'unit'}); - post = ( - <> - - {' '} - {instructions} - ); - customMessageBody.push(post); - } else { - messageBuilder.push(formatMessage({id: 'postypes.custom_open_plugin_install_post_rendered.app_installation_request_text', defaultMessage: 'You’ve received the following app installation requests:'})); - post = ( -
    - {pluginIds.map((pluginId) => { - const plugins = requestedPluginsByPluginIds[pluginId]; - const pluginName = marketplacePluginsNamesById[pluginId]; - const uniqueUserRequestsForPlugins = uniqWith(plugins, (one, two) => one.user_id === two.user_id); - const installRequests = []; - installRequests.push(createUsernameMessage(uniqueUserRequestsForPlugins)); - installRequests.push(' ' + formatMessage({id: 'postypes.custom_open_plugin_install_post_rendered.plugin_request', defaultMessage: 'requested installing the {pluginRequests} app.'}, {pluginRequests: pluginName})); - - return ( -
  • - - {' '} - -
  • - ); - })} -
- ); - - const instructions = ( - ( - - {text} - - ), - }} - />); - - customMessageBody.push(messageBuilder); - customMessageBody.push(post); - customMessageBody.push(instructions); - } - } - - return ( -
- {customMessageBody} -
- ); -} diff --git a/webapp/channels/src/components/root/root.tsx b/webapp/channels/src/components/root/root.tsx index c6c7bf2cc2..8a1809927c 100644 --- a/webapp/channels/src/components/root/root.tsx +++ b/webapp/channels/src/components/root/root.tsx @@ -19,7 +19,6 @@ import {measurePageLoadTelemetry, temporarilySetPageLoadContext, trackEvent, tra import BrowserStore from 'stores/browser_store'; import {makeAsyncComponent} from 'components/async_load'; -import OpenPluginInstallPost from 'components/custom_open_plugin_install_post_renderer'; import GlobalHeader from 'components/global_header/global_header'; import {HFRoute} from 'components/header_footer_route/header_footer_route'; import {HFTRoute, LoggedInHFTRoute} from 'components/header_footer_template_route'; @@ -360,9 +359,6 @@ export default class Root extends React.PureComponent { this.initiateMeRequests(); - // See figma design on issue https://mattermost.atlassian.net/browse/MM-43649 - this.props.actions.registerCustomPostRenderer('custom_pl_notification', OpenPluginInstallPost, 'plugin_install_post_message_renderer'); - measurePageLoadTelemetry(); trackSelectorMetrics(); diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 8f48fc8c8b..4b9a2e3d99 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -4281,9 +4281,7 @@ "marketplace_modal.install_plugins": "Install plugins", "marketplace_modal.installing": "Installing...", "marketplace_modal.list.configure": "Configure", - "marketplace_modal.list.configure.plugin": "Configure {plugin}", "marketplace_modal.list.install": "Install", - "marketplace_modal.list.install.plugin": "Install {plugin}", "marketplace_modal.list.installed": "Installed", "marketplace_modal.list.try_again": "Try Again", "marketplace_modal.list.update": "Update", @@ -4723,14 +4721,7 @@ "postlist.toast.searchHint": "Tip: Try {searchShortcut} to search this channel", "posts_view.loadMore": "Load More messages", "posts_view.newMsg": "New Messages", - "postypes.custom_open_plugin_install_post_rendered.app_installation_request_text": "You’ve received the following app installation requests:", - "postypes.custom_open_plugin_install_post_rendered.plugin_instructions": " or visit Marketplace to view all plugins.", - "postypes.custom_open_plugin_install_post_rendered.plugin_request": "requested installing the {pluginRequests} app.", - "postypes.custom_open_plugin_install_post_rendered.plugins_instructions": "Install the apps or visit Marketplace to view all plugins.", - "postypes.custom_open_pricing_modal_post_renderer.and": "and", - "postypes.custom_open_pricing_modal_post_renderer.members": "{members} members", "postypes.custom_open_pricing_modal_post_renderer.membersThatRequested": "Members that requested ", - "postypes.custom_open_pricing_modal_post_renderer.unknown": "@unknown", "pricing_modal.addons.dedicatedDB": "Dedicated database", "pricing_modal.addons.dedicatedDeployment": "Dedicated virtual secure cloud deployment (Cloud)", "pricing_modal.addons.dedicatedEncryption": "Dedicated encryption keys",