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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
71923fe311
Коммит
4cc2fed079
@@ -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={
|
||||
|
||||
136
webapp/channels/src/components/root/__snapshots__/root.test.tsx.snap
Обычный файл
136
webapp/channels/src/components/root/__snapshots__/root.test.tsx.snap
Обычный файл
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user