mirror of
https://github.com/mattermost/mattermost.git
synced 2026-09-24 16:05:00 +08:00
[MM-69593] Remove StreamlinedMarketplace feature flag (#37390)
* Remove StreamlinedMarketplace feature flag definition Co-authored-by: mattermost-code <matty-code@mattermost.com> * Remove StreamlinedMarketplace feature flag from webapp The streamlined marketplace UI is now the permanent behavior. Remove the selector, the legacy tabbed/search branch of the marketplace modal, and update the affected tests and snapshots. Co-authored-by: mattermost-code <matty-code@mattermost.com> * Remove obsolete StreamlinedMarketplace feature flag guard in cypress spec Co-authored-by: mattermost-code <matty-code@mattermost.com> * Remove unused marketplace i18n strings after flag removal Co-authored-by: mattermost-code <matty-code@mattermost.com> * Clamp marketplace pagination when listing shrinks When fetchListing() or plugin-status refetches reduce the listing size, keep the current page within the last valid page so users are not left on an empty view with no pagination footer. Co-authored-by: mattermost-code <matty-code@mattermost.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: mattermost-code <matty-code@mattermost.com>
This commit is contained in:
co-authored by
mattermost-code
Cursor Agent
parent
7870605fb1
commit
af43bfdbb0
@@ -17,7 +17,6 @@ describe('Plugin Marketplace', () => {
|
||||
|
||||
before(() => {
|
||||
cy.shouldNotRunOnCloudEdition();
|
||||
cy.shouldHaveFeatureFlag('StreamlinedMarketplace', 'false'); // https://mattermost.atlassian.net/browse/MM-54230
|
||||
cy.shouldHavePluginUploadEnabled();
|
||||
|
||||
cy.apiInitSetup().then(({team}) => {
|
||||
|
||||
@@ -799,7 +799,6 @@ const defaultServerConfig: AdminConfig = {
|
||||
OnboardingTourTips: true,
|
||||
EnableExportDirectDownload: false,
|
||||
MoveThreadsEnabled: false,
|
||||
StreamlinedMarketplace: true,
|
||||
CloudDedicatedExportUI: false,
|
||||
WebSocketEventScope: true,
|
||||
ExperimentalAuditSettingsSystemConsoleUI: true,
|
||||
|
||||
@@ -36,8 +36,6 @@ type FeatureFlags struct {
|
||||
|
||||
MoveThreadsEnabled bool
|
||||
|
||||
StreamlinedMarketplace bool
|
||||
|
||||
CloudDedicatedExportUI bool
|
||||
|
||||
WebSocketEventScope bool
|
||||
@@ -156,7 +154,6 @@ func (f *FeatureFlags) SetDefaults() {
|
||||
f.OnboardingTourTips = true
|
||||
f.EnableExportDirectDownload = false
|
||||
f.MoveThreadsEnabled = false
|
||||
f.StreamlinedMarketplace = true
|
||||
f.CloudDedicatedExportUI = false
|
||||
f.WebSocketEventScope = true
|
||||
f.ExperimentalAuditSettingsSystemConsoleUI = true
|
||||
|
||||
-1
@@ -486,7 +486,6 @@ describe('components/PluginManagement', () => {
|
||||
},
|
||||
},
|
||||
appsFeatureFlagEnabled: false,
|
||||
streamlinedMarketplaceFlagEnabled: false,
|
||||
actions: {
|
||||
uploadPlugin: jest.fn(),
|
||||
installPluginFromUrl: jest.fn(),
|
||||
|
||||
+528
-992
File diff suppressed because it is too large
Load Diff
@@ -72,9 +72,7 @@ describe('components/marketplace/', () => {
|
||||
entities: {
|
||||
general: {
|
||||
firstAdminCompleteSetup: false,
|
||||
config: {
|
||||
FeatureFlagStreamlinedMarketplace: 'false',
|
||||
},
|
||||
config: {},
|
||||
license: {
|
||||
Cloud: 'false',
|
||||
},
|
||||
@@ -169,7 +167,7 @@ describe('components/marketplace/', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('hides search, shows web marketplace banner in FeatureFlags.StreamlinedMarketplace', () => {
|
||||
test('hides search and shows web marketplace banner', () => {
|
||||
const setState = jest.fn();
|
||||
const useStateSpy = jest.spyOn(React, 'useState');
|
||||
useStateSpy.mockImplementation(() => [true, setState]);
|
||||
@@ -179,8 +177,6 @@ describe('components/marketplace/', () => {
|
||||
sampleInstalledPlugin,
|
||||
];
|
||||
|
||||
(mockState.entities.general.config as any).FeatureFlagStreamlinedMarketplace = 'true';
|
||||
|
||||
// Suppress expected errors from useState mock affecting all child components
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
try {
|
||||
@@ -197,12 +193,11 @@ describe('components/marketplace/', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("doesn't show web marketplace banner in FeatureFlags.StreamlinedMarketplace for Cloud", () => {
|
||||
test("doesn't show web marketplace banner for Cloud", () => {
|
||||
const setState = jest.fn();
|
||||
const useStateSpy = jest.spyOn(React, 'useState');
|
||||
useStateSpy.mockImplementation(() => [true, setState]);
|
||||
|
||||
(mockState.entities.general.config as any).FeatureFlagStreamlinedMarketplace = 'true';
|
||||
mockState.entities.general.license.Cloud = 'true';
|
||||
|
||||
// Suppress expected errors from useState mock affecting all child components
|
||||
|
||||
@@ -2,31 +2,25 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React, {useCallback, useEffect, useRef, useState} from 'react';
|
||||
import type {ReactNode} from 'react';
|
||||
import {Tabs, Tab} from 'react-bootstrap';
|
||||
import type {SelectCallback} from 'react-bootstrap';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {MagnifyIcon} from '@mattermost/compass-icons/components';
|
||||
import {FooterPagination, GenericModal} from '@mattermost/components';
|
||||
|
||||
import {getPluginStatuses} from 'mattermost-redux/actions/admin';
|
||||
import {setFirstAdminVisitMarketplaceStatus} from 'mattermost-redux/actions/general';
|
||||
import {getFirstAdminVisitMarketplaceStatus, getLicense} from 'mattermost-redux/selectors/entities/general';
|
||||
import {streamlinedMarketplaceEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
|
||||
import {fetchListing, filterListing} from 'actions/marketplace';
|
||||
import {fetchListing} from 'actions/marketplace';
|
||||
import {closeModal} from 'actions/views/modals';
|
||||
import {getListing, getInstalledListing} from 'selectors/views/marketplace';
|
||||
import {getListing} from 'selectors/views/marketplace';
|
||||
import {isModalOpen} from 'selectors/views/modals';
|
||||
|
||||
import usePluginStatusesSync from 'components/common/hooks/usePluginStatusesSync';
|
||||
import LoadingScreen from 'components/loading_screen';
|
||||
import Input, {SIZE} from 'components/widgets/inputs/input/input';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import {isCloudLicense} from 'utils/license_utils';
|
||||
@@ -38,13 +32,6 @@ import WebMarketplaceBanner from './web_marketplace_banner';
|
||||
|
||||
import './marketplace_modal.scss';
|
||||
|
||||
const MarketplaceTabs = {
|
||||
ALL_LISTING: 'all',
|
||||
INSTALLED_LISTING: 'installed',
|
||||
};
|
||||
|
||||
const SEARCH_TIMEOUT_MILLISECONDS = 200;
|
||||
|
||||
const linkConsole = (msg: ReactNode[]): ReactNode => (
|
||||
<Link to='/admin_console/plugins/plugin_management'>
|
||||
{msg}
|
||||
@@ -58,17 +45,13 @@ const MarketplaceModal = () => {
|
||||
|
||||
const show = useSelector((state: GlobalState) => isModalOpen(state, ModalIdentifiers.PLUGIN_MARKETPLACE));
|
||||
const listing = useSelector(getListing);
|
||||
const installedListing = useSelector(getInstalledListing);
|
||||
|
||||
// Refetch plugin statuses while the modal is open whenever the server signals a change.
|
||||
const pluginStatuses = usePluginStatusesSync();
|
||||
const hasFirstAdminVisitedMarketplace = useSelector(getFirstAdminVisitMarketplaceStatus);
|
||||
const isStreamlinedMarketplaceEnabled = useSelector(streamlinedMarketplaceEnabled);
|
||||
const license = useSelector(getLicense);
|
||||
const isCloud = isCloudLicense(license);
|
||||
|
||||
const [tabKey, setTabKey] = useState(MarketplaceTabs.ALL_LISTING);
|
||||
const [filter, setFilter] = useState('');
|
||||
const [page, setPage] = useState(0);
|
||||
const [hasLoaded, setHasLoaded] = useState(false);
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
@@ -84,16 +67,6 @@ const MarketplaceModal = () => {
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
const doSearch = useCallback(async () => {
|
||||
const {error} = await dispatch(filterListing(filter));
|
||||
|
||||
if (error) {
|
||||
setServerError(true);
|
||||
}
|
||||
}, [filter]);
|
||||
|
||||
const debouncedSearch = debounce(doSearch, SEARCH_TIMEOUT_MILLISECONDS);
|
||||
|
||||
useEffect(() => {
|
||||
async function doFetch() {
|
||||
await dispatch(getPluginStatuses());
|
||||
@@ -115,11 +88,11 @@ const MarketplaceModal = () => {
|
||||
}, [pluginStatuses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasLoaded) {
|
||||
debouncedSearch();
|
||||
setPage(0);
|
||||
const maxPage = Math.max(0, Math.ceil(listing.length / ITEMS_PER_PAGE) - 1);
|
||||
if (page > maxPage) {
|
||||
setPage(maxPage);
|
||||
}
|
||||
}, [filter]);
|
||||
}, [listing.length, page]);
|
||||
|
||||
const scrollListToTop = useCallback(() => {
|
||||
if (listRef.current) {
|
||||
@@ -131,20 +104,6 @@ const MarketplaceModal = () => {
|
||||
dispatch(closeModal(ModalIdentifiers.PLUGIN_MARKETPLACE));
|
||||
};
|
||||
|
||||
const handleChangeTab: SelectCallback = useCallback((tabKey) => {
|
||||
setTabKey(tabKey);
|
||||
setPage(0);
|
||||
scrollListToTop();
|
||||
}, [scrollListToTop]);
|
||||
|
||||
const handleOnChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFilter(event.target.value);
|
||||
}, []);
|
||||
|
||||
const handleOnClear = useCallback(() => {
|
||||
setFilter('');
|
||||
}, []);
|
||||
|
||||
const handleOnNextPage = useCallback(() => {
|
||||
setPage(page + 1);
|
||||
scrollListToTop();
|
||||
@@ -155,65 +114,35 @@ const MarketplaceModal = () => {
|
||||
scrollListToTop();
|
||||
}, [page, scrollListToTop]);
|
||||
|
||||
const handleNoResultsButtonClick = useCallback(() => {
|
||||
handleChangeTab(MarketplaceTabs.ALL_LISTING);
|
||||
}, [handleChangeTab]);
|
||||
|
||||
const getHeaderInput = useCallback(() => {
|
||||
if (isStreamlinedMarketplaceEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
id='searchMarketplaceTextbox'
|
||||
name='searchMarketplaceTextbox'
|
||||
containerClassName='marketplace-modal-search'
|
||||
inputClassName='search_input'
|
||||
type='text'
|
||||
inputSize={SIZE.LARGE}
|
||||
inputPrefix={<MagnifyIcon size={24}/>}
|
||||
placeholder={formatMessage({id: 'marketplace_modal.search', defaultMessage: 'Search marketplace'})}
|
||||
useLegend={false}
|
||||
autoFocus={true}
|
||||
clearable={true}
|
||||
value={filter}
|
||||
onChange={handleOnChange}
|
||||
onClear={handleOnClear}
|
||||
/>
|
||||
);
|
||||
}, [filter, handleOnChange, handleOnClear]);
|
||||
|
||||
const getFooterContent = useCallback(() => {
|
||||
if (isStreamlinedMarketplaceEnabled && listing.length <= ITEMS_PER_PAGE) {
|
||||
if (listing.length <= ITEMS_PER_PAGE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FooterPagination
|
||||
page={page}
|
||||
total={tabKey === MarketplaceTabs.ALL_LISTING ? listing.length : installedListing.length}
|
||||
total={listing.length}
|
||||
itemsPerPage={ITEMS_PER_PAGE}
|
||||
onNextPage={handleOnNextPage}
|
||||
onPreviousPage={handleOnPreviousPage}
|
||||
/>
|
||||
);
|
||||
}, [installedListing.length, listing.length, page, handleOnNextPage, handleOnPreviousPage, tabKey, isStreamlinedMarketplaceEnabled]);
|
||||
}, [listing.length, page, handleOnNextPage, handleOnPreviousPage]);
|
||||
|
||||
const getAppendedContent = useCallback(() => {
|
||||
if (!isStreamlinedMarketplaceEnabled || isCloud) {
|
||||
if (isCloud) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <WebMarketplaceBanner/>;
|
||||
}, [isStreamlinedMarketplaceEnabled, isCloud]);
|
||||
}, [isCloud]);
|
||||
|
||||
return (
|
||||
<GenericModal
|
||||
id='marketplace-modal'
|
||||
className={classNames('marketplace-modal', {
|
||||
'streamlined-marketplace': isStreamlinedMarketplaceEnabled,
|
||||
'with-web-marketplace-link': isStreamlinedMarketplaceEnabled && !isCloud,
|
||||
className={classNames('marketplace-modal', 'streamlined-marketplace', {
|
||||
'with-web-marketplace-link': !isCloud,
|
||||
})}
|
||||
modalHeaderText={formatMessage({id: 'marketplace_modal.title', defaultMessage: 'App Marketplace'})}
|
||||
ariaLabel={formatMessage({id: 'marketplace_modal.title', defaultMessage: 'App Marketplace'})}
|
||||
@@ -229,75 +158,21 @@ const MarketplaceModal = () => {
|
||||
show={show}
|
||||
compassDesign={true}
|
||||
bodyPadding={false}
|
||||
bodyDivider={isStreamlinedMarketplaceEnabled}
|
||||
bodyDivider={true}
|
||||
footerDivider={true}
|
||||
onExited={handleOnClose}
|
||||
footerContent={getFooterContent()}
|
||||
appendedContent={getAppendedContent()}
|
||||
headerInput={getHeaderInput()}
|
||||
>
|
||||
{isStreamlinedMarketplaceEnabled ? (
|
||||
<>
|
||||
{loading ? (
|
||||
<LoadingScreen className='loading'/>
|
||||
) : (
|
||||
<MarketplaceList
|
||||
listRef={listRef}
|
||||
listing={listing}
|
||||
page={page}
|
||||
filter={filter}
|
||||
noResultsMessage={formatMessage({id: 'marketplace_modal.no_plugins', defaultMessage: 'No plugins found'})}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
{loading ? (
|
||||
<LoadingScreen className='loading'/>
|
||||
) : (
|
||||
<Tabs
|
||||
id='marketplaceTabs'
|
||||
className='tabs'
|
||||
defaultActiveKey={MarketplaceTabs.ALL_LISTING}
|
||||
activeKey={tabKey}
|
||||
onSelect={handleChangeTab}
|
||||
unmountOnExit={true}
|
||||
>
|
||||
<Tab
|
||||
eventKey={MarketplaceTabs.ALL_LISTING}
|
||||
title={formatMessage({id: 'marketplace_modal.tabs.all_listing', defaultMessage: 'All'})}
|
||||
>
|
||||
{loading ? (
|
||||
<LoadingScreen className='loading'/>
|
||||
) : (
|
||||
<MarketplaceList
|
||||
listRef={listRef}
|
||||
listing={listing}
|
||||
page={page}
|
||||
filter={filter}
|
||||
noResultsMessage={formatMessage({id: 'marketplace_modal.no_plugins', defaultMessage: 'No plugins found'})}
|
||||
/>
|
||||
)}
|
||||
</Tab>
|
||||
<Tab
|
||||
eventKey={MarketplaceTabs.INSTALLED_LISTING}
|
||||
title={formatMessage(
|
||||
{id: 'marketplace_modal.tabs.installed_listing', defaultMessage: 'Installed ({count})'},
|
||||
{count: installedListing.length},
|
||||
)}
|
||||
>
|
||||
<MarketplaceList
|
||||
listRef={listRef}
|
||||
listing={installedListing}
|
||||
page={page}
|
||||
filter={filter}
|
||||
noResultsMessage={formatMessage({
|
||||
id: 'marketplace_modal.no_plugins_installed',
|
||||
defaultMessage: 'No plugins installed found',
|
||||
})}
|
||||
noResultsAction={{
|
||||
label: formatMessage({id: 'marketplace_modal.install_plugins', defaultMessage: 'Install plugins'}),
|
||||
onClick: handleNoResultsButtonClick,
|
||||
}}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
<MarketplaceList
|
||||
listRef={listRef}
|
||||
listing={listing}
|
||||
page={page}
|
||||
noResultsMessage={formatMessage({id: 'marketplace_modal.no_plugins', defaultMessage: 'No plugins found'})}
|
||||
/>
|
||||
)}
|
||||
</GenericModal>
|
||||
);
|
||||
|
||||
@@ -5795,7 +5795,6 @@
|
||||
"marketplace_command.no_permission": "You do not have the appropriate permissions to access the marketplace.",
|
||||
"marketplace_modal_list.no_plugins_filter": "No results for \"{filter}\"",
|
||||
"marketplace_modal.app_error": "Error connecting to the marketplace server. Please check your settings in the <linkConsole>System Console</linkConsole>.",
|
||||
"marketplace_modal.install_plugins": "Install plugins",
|
||||
"marketplace_modal.installing": "Installing...",
|
||||
"marketplace_modal.list.configure": "Configure",
|
||||
"marketplace_modal.list.install": "Install",
|
||||
@@ -5811,10 +5810,6 @@
|
||||
"marketplace_modal.list.update_confirmation.message.warning_major_version_with_release_notes": "This update may contain breaking changes. Consult the <a>release notes</a> before upgrading.",
|
||||
"marketplace_modal.list.update_confirmation.title": "Confirm Plugin Update",
|
||||
"marketplace_modal.no_plugins": "No plugins found",
|
||||
"marketplace_modal.no_plugins_installed": "No plugins installed found",
|
||||
"marketplace_modal.search": "Search marketplace",
|
||||
"marketplace_modal.tabs.all_listing": "All",
|
||||
"marketplace_modal.tabs.installed_listing": "Installed ({count})",
|
||||
"marketplace_modal.title": "App Marketplace",
|
||||
"marketplace_modal.web_marketplace_link.desc": "Connect the tools you use most to Mattermost",
|
||||
"marketplace_modal.web_marketplace_link.title": "Discover community integrations",
|
||||
|
||||
@@ -330,10 +330,6 @@ export function moveThreadsEnabled(state: GlobalState): boolean {
|
||||
return getFeatureFlagValue(state, 'MoveThreadsEnabled') === 'true' && getLicense(state).IsLicensed === 'true';
|
||||
}
|
||||
|
||||
export function streamlinedMarketplaceEnabled(state: GlobalState): boolean {
|
||||
return getFeatureFlagValue(state, 'StreamlinedMarketplace') === 'true';
|
||||
}
|
||||
|
||||
export function getWysiwygEditorPreference(state: GlobalState): boolean {
|
||||
const featureEnabled = getFeatureFlagValue(state, 'WysiwygEditor') === 'true';
|
||||
if (!featureEnabled) {
|
||||
|
||||
Reference in New Issue
Block a user