Adds support for public routes on shared boards (#22710)

* Adds support for public routes on shared boards

* Fix linter

* Address review comments

* Update playbooks registerProduct call

* Use boards product config for setting

* update additional product locations for parameter changes

* fixes for read-only when logged in

* turn off global header and don't initialize plugin if shared board

* fix unit tests

* revert package-lock.json

* more fixes

* Remove FF check for system console setting for boards

* update tests, Product boards is displayed in system console

* only check products section of config

* update test for config change

---------

Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Miguel de la Cruz
2023-04-06 20:24:32 +02:00
коммит произвёл GitHub
родитель 71923fe311
Коммит 4cc2fed079
27 изменённых файлов: 552 добавлений и 49 удалений

Просмотреть файл

@@ -5746,7 +5746,6 @@ const AdminDefinition = {
sectionTitle: t('admin.sidebar.products'),
sectionTitleDefault: 'Products',
isHidden: it.any(
it.configIsFalse('FeatureFlags', 'BoardsProduct'),
it.not(it.userHasReadPermissionOnSomeResources(RESOURCE_KEYS.PRODUCTS)),
),
boards: {

Просмотреть файл

@@ -1072,6 +1072,37 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={
@@ -1775,6 +1806,37 @@ exports[`components/AdminSidebar should match snapshot with workspace optimizati
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={
@@ -2533,6 +2595,37 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={
@@ -3236,6 +3329,37 @@ exports[`components/AdminSidebar should match snapshot, render plugins without a
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={
@@ -4038,6 +4162,37 @@ exports[`components/AdminSidebar should match snapshot, with license (with all f
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={
@@ -4973,6 +5128,37 @@ exports[`components/AdminSidebar should match snapshot, with license (without an
title="Plugin 0"
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="products"
icon={
<ProductsIcon
className="category-icon fa"
color="currentColor"
size={16}
/>
}
key="products"
parentLink="/admin_console"
sectionClass=""
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Products"
id="admin.sidebar.products"
/>
}
>
<AdminSidebarSection
definitionKey="products.boards"
key="products.boards"
name="products/boards"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Boards"
id="admin.sidebar.boards"
/>
}
/>
</AdminSidebarCategory>
<AdminSidebarCategory
definitionKey="integrations"
icon={

Просмотреть файл

@@ -0,0 +1,136 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/Root Routes Should mount public product routes 1`] = `
<RootProvider>
<Switch>
<Route
component={[Function]}
path="/error"
/>
<HFRoute
component={[Function]}
path="/login"
/>
<HFRoute
component={[Function]}
path="/access_problem"
/>
<HFTRoute
component={[Function]}
path="/reset_password"
/>
<HFTRoute
component={[Function]}
path="/reset_password_complete"
/>
<HFRoute
component={[Function]}
path="/signup_user_complete"
/>
<HFRoute
component={[Function]}
path="/should_verify_email"
/>
<HFRoute
component={[Function]}
path="/do_verify_email"
/>
<HFTRoute
component={[Function]}
path="/claim"
/>
<HFTRoute
component={[Function]}
path="/help"
/>
<LoggedInRoute
component={[Function]}
path="/terms_of_service"
/>
<Route
component={[Function]}
path="/landing"
/>
<Route
path="/admin_console"
>
<Switch>
<LoggedInRoute
component={[Function]}
path="/admin_console"
theme={Object {}}
/>
<Connect(RootRedirect) />
</Switch>
</Route>
<LoggedInHFTRoute
component={[Function]}
path="/select_team"
/>
<LoggedInHFTRoute
component={[Function]}
path="/oauth/authorize"
/>
<LoggedInHFTRoute
component={[Function]}
path="/create_team"
/>
<LoggedInRoute
component={[Function]}
path="/mfa"
/>
<LoggedInRoute
component={[Function]}
path="/preparing-workspace"
/>
<Redirect
from="/_redirect/integrations/:subpath*"
to="/myTeam/integrations/:subpath*"
/>
<Redirect
from="/_redirect/pl/:postid"
to="/myTeam/pl/:postid"
/>
<CompassThemeProvider
theme={Object {}}
>
<Connect(ModalController) />
<Connect(_class) />
<Connect(SystemNotice) />
<GlobalHeader />
<CloudEffectsWrapper />
<withRouter(Connect(TeamSidebar)) />
<DelinquencyModalController />
<Switch>
<Route
key="productwithpublic-public"
path="/productwithpublic/public"
render={[Function]}
/>
<Route
key="productwithpublic"
path="/productwithpublic"
render={[Function]}
/>
<Route
key="productwithoutpublic"
path="/productwithoutpublic"
render={[Function]}
/>
<LoggedInRoute
component={[Function]}
path="/:team"
theme={Object {}}
/>
<Connect(RootRedirect) />
</Switch>
<Connect(Pluggable)
pluggableName="Global"
/>
<withRouter(Connect(Component)) />
<AppBar />
<Connect(SidebarRightMenu) />
</CompassThemeProvider>
</Switch>
</RootProvider>
`;

Просмотреть файл

@@ -15,6 +15,7 @@ import Root from 'components/root/root';
import * as GlobalActions from 'actions/global_actions';
import Constants, {StoragePrefixes, WindowSizes} from 'utils/constants';
import matchMedia from 'tests/helpers/match_media.mock';
import {ProductComponent} from 'types/store/plugins';
jest.mock('rudder-sdk-js', () => ({
identify: jest.fn(),
@@ -64,7 +65,7 @@ describe('components/Root', () => {
registerCustomPostRenderer: jest.fn(),
initializeProducts: jest.fn(),
},
permalinkRedirectTeamName: '',
permalinkRedirectTeamName: 'myTeam',
showLaunchingWorkspace: false,
plugins: [],
products: [],
@@ -325,4 +326,33 @@ describe('components/Root', () => {
wrapper.unmount();
});
});
describe('Routes', () => {
test('Should mount public product routes', () => {
const mainComponent = () => (<p>{'TestMainComponent'}</p>);
const publicComponent = () => (<p>{'TestPublicProduct'}</p>);
const props = {
...baseProps,
products: [{
id: 'productwithpublic',
baseURL: '/productwithpublic',
mainComponent,
publicComponent,
} as unknown as ProductComponent,
{
id: 'productwithoutpublic',
baseURL: '/productwithoutpublic',
mainComponent,
publicComponent: null,
} as unknown as ProductComponent],
};
const wrapper = shallow(<Root {...props}/>);
(wrapper.instance() as any).setState({configLoaded: true});
expect(wrapper).toMatchSnapshot();
wrapper.unmount();
});
});
});

Просмотреть файл

@@ -650,6 +650,23 @@ export default class Root extends React.PureComponent<Props, State> {
<TeamSidebar/>
<DelinquencyModalController/>
<Switch>
{this.props.products?.filter((product) => Boolean(product.publicComponent)).map((product) => (
<Route
key={`${product.id}-public`}
path={`${product.baseURL}/public`}
render={(props) => {
return (
<Pluggable
pluggableName={'Product'}
subComponentName={'publicComponent'}
pluggableId={product.id}
css={{gridArea: 'center'}}
{...props}
/>
);
}}
/>
))}
{this.props.products?.map((product) => (
<Route
key={product.id}

Просмотреть файл

@@ -41,7 +41,7 @@ type Props = {
*
* Only supported when pluggableName is "Product".
*/
subComponentName?: 'mainComponent' | 'headerCentreComponent' | 'headerRightComponent';
subComponentName?: 'mainComponent' | 'publicComponent' | 'headerCentreComponent' | 'headerRightComponent';
/*
* Accept any other prop to pass onto the plugin component

Просмотреть файл

@@ -986,6 +986,7 @@ export default class PluginRegistry {
'showTeamSidebar',
'showAppBar',
'wrapped',
'publicComponent',
], ({
baseURL,
switcherIcon,
@@ -997,6 +998,7 @@ export default class PluginRegistry {
showTeamSidebar = false,
showAppBar = false,
wrapped = true,
publicComponent,
}: Omit<ProductComponent, 'id' | 'pluginId'>) => {
const id = generateId();
@@ -1016,6 +1018,7 @@ export default class PluginRegistry {
showTeamSidebar,
showAppBar,
wrapped,
publicComponent,
},
});

Просмотреть файл

@@ -187,6 +187,11 @@ export type ProductComponent = {
*/
mainComponent: React.ComponentType;
/**
* The public component to be displayed when a public route is active.
*/
publicComponent: React.ComponentType | null;
/**
* A component to fill the generic area in the center of
* the global header when your route is active.

Просмотреть файл

@@ -371,6 +371,7 @@ export class TestHelper {
showTeamSidebar: false,
showAppBar: false,
wrapped: true,
publicComponent: null,
};
}