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
@@ -77,10 +77,13 @@ func TestSetConfiguration(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("test enable shared boards", func(t *testing.T) {
|
t.Run("test enable shared boards", func(t *testing.T) {
|
||||||
|
baseProductSettings := &model.ProductSettings{
|
||||||
|
EnablePublicSharedBoards: &falseRef,
|
||||||
|
}
|
||||||
|
|
||||||
mmConfig := baseConfig
|
mmConfig := baseConfig
|
||||||
mmConfig.PluginSettings.Plugins = make(map[string]map[string]interface{})
|
mmConfig.ProductSettings = *baseProductSettings
|
||||||
mmConfig.PluginSettings.Plugins[server.PluginName] = make(map[string]interface{})
|
mmConfig.ProductSettings.EnablePublicSharedBoards = &boolTrue
|
||||||
mmConfig.PluginSettings.Plugins[server.PluginName][server.SharedBoardsName] = true
|
|
||||||
config := server.CreateBoardsConfig(*mmConfig, "", "")
|
config := server.CreateBoardsConfig(*mmConfig, "", "")
|
||||||
assert.Equal(t, true, config.EnablePublicSharedBoards)
|
assert.Equal(t, true, config.EnablePublicSharedBoards)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ func CreateBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
enablePublicSharedBoards := false
|
enablePublicSharedBoards := false
|
||||||
if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true {
|
if mmconfig.ProductSettings.EnablePublicSharedBoards != nil {
|
||||||
enablePublicSharedBoards = true
|
enablePublicSharedBoards = *mmconfig.ProductSettings.EnablePublicSharedBoards
|
||||||
}
|
}
|
||||||
|
|
||||||
enableBoardsDeletion := false
|
enableBoardsDeletion := false
|
||||||
|
|||||||
@@ -873,7 +873,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="octo-board-header-cell KanbanColumnHeader"
|
class="octo-board-header-cell KanbanColumnHeader"
|
||||||
draggable="true"
|
draggable="false"
|
||||||
style="opacity: 1;"
|
style="opacity: 1;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -901,7 +901,7 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="octo-board-header-cell KanbanColumnHeader"
|
class="octo-board-header-cell KanbanColumnHeader"
|
||||||
draggable="true"
|
draggable="false"
|
||||||
style="opacity: 1;"
|
style="opacity: 1;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -1927,7 +1927,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="octo-board-header-cell KanbanColumnHeader"
|
class="octo-board-header-cell KanbanColumnHeader"
|
||||||
draggable="true"
|
draggable="false"
|
||||||
style="opacity: 1;"
|
style="opacity: 1;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -1955,7 +1955,7 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="octo-board-header-cell KanbanColumnHeader"
|
class="octo-board-header-cell KanbanColumnHeader"
|
||||||
draggable="true"
|
draggable="false"
|
||||||
style="opacity: 1;"
|
style="opacity: 1;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -29,7 +29,14 @@ jest.mock('react-router-dom', () => {
|
|||||||
return {
|
return {
|
||||||
...originalModule,
|
...originalModule,
|
||||||
useRouteMatch: jest.fn(() => {
|
useRouteMatch: jest.fn(() => {
|
||||||
return {url: '/board/view'}
|
return {
|
||||||
|
params: {
|
||||||
|
teamId: 'team1',
|
||||||
|
boardId: 'boardId1',
|
||||||
|
viewId: 'viewId1',
|
||||||
|
cardId: 'cardId1',
|
||||||
|
},
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -469,7 +469,7 @@ exports[`src/components/kanban/kanbanColumnHeader should match snapshot readonly
|
|||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="octo-board-header-cell KanbanColumnHeader"
|
class="octo-board-header-cell KanbanColumnHeader"
|
||||||
draggable="true"
|
draggable="false"
|
||||||
style="opacity: 1;"
|
style="opacity: 1;"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -52,8 +52,12 @@ const defaultProperty: IPropertyTemplate = {
|
|||||||
export default function KanbanColumnHeader(props: Props): JSX.Element {
|
export default function KanbanColumnHeader(props: Props): JSX.Element {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const {board, activeView, group, groupByProperty} = props
|
const {board, activeView, group, groupByProperty} = props
|
||||||
|
let readonly = props.readonly
|
||||||
|
if(!readonly){
|
||||||
|
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||||
|
}
|
||||||
|
|
||||||
const [groupTitle, setGroupTitle] = useState(group.option.value)
|
const [groupTitle, setGroupTitle] = useState(group.option.value)
|
||||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
|
||||||
const canEditOption = groupByProperty?.type !== 'person' && group.option.id
|
const canEditOption = groupByProperty?.type !== 'person' && group.option.id
|
||||||
|
|
||||||
const headerRef = useRef<HTMLDivElement>(null)
|
const headerRef = useRef<HTMLDivElement>(null)
|
||||||
@@ -79,7 +83,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
|||||||
setGroupTitle(group.option.value)
|
setGroupTitle(group.option.value)
|
||||||
}, [group.option.value])
|
}, [group.option.value])
|
||||||
|
|
||||||
if (canEditBoardProperties) {
|
if (!readonly) {
|
||||||
drop(drag(headerRef))
|
drop(drag(headerRef))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +101,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
|||||||
ref={headerRef}
|
ref={headerRef}
|
||||||
style={{opacity: isDragging ? 0.5 : 1}}
|
style={{opacity: isDragging ? 0.5 : 1}}
|
||||||
className={className}
|
className={className}
|
||||||
draggable={!props.readonly && canEditBoardProperties}
|
draggable={!readonly}
|
||||||
>
|
>
|
||||||
{!group.option.id &&
|
{!group.option.id &&
|
||||||
<Label
|
<Label
|
||||||
@@ -133,7 +137,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setGroupTitle(group.option.value)
|
setGroupTitle(group.option.value)
|
||||||
}}
|
}}
|
||||||
readonly={props.readonly || !canEditBoardProperties}
|
readonly={readonly}
|
||||||
spellCheck={true}
|
spellCheck={true}
|
||||||
/>
|
/>
|
||||||
</Label>}
|
</Label>}
|
||||||
@@ -145,7 +149,7 @@ export default function KanbanColumnHeader(props: Props): JSX.Element {
|
|||||||
onMenuClose={props.onCalculationMenuClose}
|
onMenuClose={props.onCalculationMenuClose}
|
||||||
onMenuOpen={props.onCalculationMenuOpen}
|
onMenuOpen={props.onCalculationMenuOpen}
|
||||||
cardProperties={board.cardProperties}
|
cardProperties={board.cardProperties}
|
||||||
readonly={props.readonly || !canEditBoardProperties}
|
readonly={readonly}
|
||||||
onChange={(data: {calculation: string, propertyId: string}) => {
|
onChange={(data: {calculation: string, propertyId: string}) => {
|
||||||
if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {
|
if (data.calculation === calculationValue && data.propertyId === calculationProperty.id) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -983,11 +983,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard and click Regene
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="shareUrl"
|
class="shareUrl"
|
||||||
href="http://localhost:8065/team/team-id/shared/1/1?r=anotherToken"
|
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
http://localhost:8065/team/team-id/shared/1/1?r=anotherToken
|
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=anotherToken
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
class="octo-tooltip tooltip-top"
|
class="octo-tooltip tooltip-top"
|
||||||
@@ -3188,11 +3188,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoard, and click switc
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="shareUrl"
|
class="shareUrl"
|
||||||
href="http://localhost:8065/team/team-id/shared/1/1?r=oneToken"
|
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
http://localhost:8065/team/team-id/shared/1/1?r=oneToken
|
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=oneToken
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
class="octo-tooltip tooltip-top"
|
class="octo-tooltip tooltip-top"
|
||||||
@@ -3461,11 +3461,11 @@ exports[`src/components/shareBoard/shareBoard return shareBoardComponent and cli
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
class="shareUrl"
|
class="shareUrl"
|
||||||
href="http://localhost:8065/team/team-id/shared/1/1?r=aToken"
|
href="http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
http://localhost:8065/team/team-id/shared/1/1?r=aToken
|
http://localhost:8065/boards/public/team/team-id/shared/1/1?r=aToken
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div
|
||||||
class="octo-tooltip tooltip-top"
|
class="octo-tooltip tooltip-top"
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export default function ShareBoardDialog(props: Props): JSX.Element {
|
|||||||
viewId: match.params.viewId,
|
viewId: match.params.viewId,
|
||||||
teamId: match.params.teamId,
|
teamId: match.params.teamId,
|
||||||
})
|
})
|
||||||
shareUrl.pathname = newPath
|
shareUrl.pathname = `/boards/public${newPath}`
|
||||||
|
|
||||||
const boardPath = generatePath('/team/:teamId/:boardId/:viewId', {
|
const boardPath = generatePath('/team/:teamId/:boardId/:viewId', {
|
||||||
boardId: match.params.boardId,
|
boardId: match.params.boardId,
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ jest.mock('react-router-dom', () => {
|
|||||||
...originalModule,
|
...originalModule,
|
||||||
useRouteMatch: jest.fn(() => {
|
useRouteMatch: jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
teamId: 'team1',
|
params: {
|
||||||
boardId: 'boardId1',
|
teamId: 'team1',
|
||||||
viewId: 'viewId1',
|
boardId: 'boardId1',
|
||||||
cardId: 'cardId1',
|
viewId: 'viewId1',
|
||||||
|
cardId: 'cardId1',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
import React, {useCallback} from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {FormattedMessage} from 'react-intl'
|
import {FormattedMessage} from 'react-intl'
|
||||||
import {generatePath, useRouteMatch, useHistory} from 'react-router-dom'
|
import {generatePath, useRouteMatch} from 'react-router-dom'
|
||||||
|
|
||||||
import Button from 'src/widgets/buttons/button'
|
import Button from 'src/widgets/buttons/button'
|
||||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
|
import TelemetryClient, {TelemetryActions, TelemetryCategory} from 'src/telemetry/telemetryClient'
|
||||||
@@ -12,14 +12,17 @@ import './shareBoardLoginButton.scss'
|
|||||||
|
|
||||||
const ShareBoardLoginButton = () => {
|
const ShareBoardLoginButton = () => {
|
||||||
const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>()
|
const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>()
|
||||||
const history = useHistory()
|
|
||||||
|
|
||||||
const redirectQueryParam = 'r=' + encodeURIComponent(generatePath('/:boardId?/:viewId?/:cardId?', match.params))
|
// Mattermost login doesn't respect the redirect query parameter
|
||||||
const loginPath = '/login?' + redirectQueryParam
|
// if the user is already logged in, so we send the user to the
|
||||||
|
// board and if they are not logged in, the webapp will take care
|
||||||
|
// of the redirection
|
||||||
|
const baseURL = window.location.href.split('/boards/public')[0]
|
||||||
|
const loginPath = `${baseURL}/${generatePath('/boards/team/:teamId/:boardId?/:viewId?/:cardId?', match.params)}`
|
||||||
|
|
||||||
const onLoginClick = useCallback(() => {
|
const onLoginClick = useCallback(() => {
|
||||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin)
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin)
|
||||||
history.push(loginPath)
|
location.assign(loginPath)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -59,9 +59,12 @@ const ViewHeader = (props: Props) => {
|
|||||||
const [showFilter, setShowFilter] = useState(false)
|
const [showFilter, setShowFilter] = useState(false)
|
||||||
const [lockFilterOnClose, setLockFilterOnClose] = useState(false)
|
const [lockFilterOnClose, setLockFilterOnClose] = useState(false)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
|
||||||
|
|
||||||
const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props
|
const {board, activeView, views, groupByProperty, cards, dateDisplayProperty} = props
|
||||||
|
let readonly = props.readonly
|
||||||
|
if(!readonly){
|
||||||
|
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||||
|
}
|
||||||
|
|
||||||
const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table'
|
const withGroupBy = activeView.fields.viewType === 'board' || activeView.fields.viewType === 'table'
|
||||||
const withDisplayBy = activeView.fields.viewType === 'calendar'
|
const withDisplayBy = activeView.fields.viewType === 'calendar'
|
||||||
@@ -137,7 +140,7 @@ const ViewHeader = (props: Props) => {
|
|||||||
}}
|
}}
|
||||||
onChange={setViewTitle}
|
onChange={setViewTitle}
|
||||||
saveOnEsc={true}
|
saveOnEsc={true}
|
||||||
readonly={props.readonly || !canEditBoardProperties}
|
readonly={readonly}
|
||||||
spellCheck={true}
|
spellCheck={true}
|
||||||
autoExpand={false}
|
autoExpand={false}
|
||||||
/>
|
/>
|
||||||
@@ -148,7 +151,7 @@ const ViewHeader = (props: Props) => {
|
|||||||
board={board}
|
board={board}
|
||||||
activeView={activeView}
|
activeView={activeView}
|
||||||
views={views}
|
views={views}
|
||||||
readonly={props.readonly || !canEditBoardProperties}
|
readonly={readonly}
|
||||||
allowCreateView={allowCreateView}
|
allowCreateView={allowCreateView}
|
||||||
/>
|
/>
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
@@ -159,7 +162,7 @@ const ViewHeader = (props: Props) => {
|
|||||||
|
|
||||||
<div className='octo-spacer'/>
|
<div className='octo-spacer'/>
|
||||||
|
|
||||||
{!props.readonly && canEditBoardProperties &&
|
{!readonly &&
|
||||||
<>
|
<>
|
||||||
{/* Card properties */}
|
{/* Card properties */}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,11 @@ const ViewTitle = (props: Props) => {
|
|||||||
}, [board.id, board.icon])
|
}, [board.id, board.icon])
|
||||||
const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription])
|
const onShowDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), true), [board.id, board.showDescription])
|
||||||
const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription])
|
const onHideDescription = useCallback(() => mutator.showBoardDescription(board.id, Boolean(board.showDescription), false), [board.id, board.showDescription])
|
||||||
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
|
||||||
|
|
||||||
const readonly = props.readonly || !canEditBoardProperties
|
let readonly = props.readonly
|
||||||
|
if(!readonly){
|
||||||
|
readonly = !useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
|
||||||
|
}
|
||||||
|
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ jest.mock('react-router-dom', () => {
|
|||||||
useRouteMatch: jest.fn(() => {
|
useRouteMatch: jest.fn(() => {
|
||||||
return {
|
return {
|
||||||
params: {
|
params: {
|
||||||
|
teamId: board.teamId,
|
||||||
boardId: board.id,
|
boardId: board.id,
|
||||||
viewId: activeView.id,
|
viewId: activeView.id,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ export const useHasPermissions = (teamId: string, boardId: string, permissions:
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const member = useAppSelector(getMyBoardMembership(boardId))
|
|
||||||
const board = useAppSelector(getBoard(boardId))
|
const board = useAppSelector(getBoard(boardId))
|
||||||
|
|
||||||
if (!board) {
|
if (!board) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const member = useAppSelector(getMyBoardMembership(boardId))
|
||||||
if (!member) {
|
if (!member) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ windowAny.baseURL = '/plugins/boards'
|
|||||||
windowAny.frontendBaseURL = '/boards'
|
windowAny.frontendBaseURL = '/boards'
|
||||||
|
|
||||||
import App from 'src/app'
|
import App from 'src/app'
|
||||||
|
import PublicApp, {publicBaseURL} from 'src/public/app'
|
||||||
|
|
||||||
import store from 'src/store'
|
import store from 'src/store'
|
||||||
import WithWebSockets from 'src/components/withWebSockets'
|
import WithWebSockets from 'src/components/withWebSockets'
|
||||||
@@ -175,10 +176,44 @@ const MainApp = (props: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PublicMainApp = () => {
|
||||||
|
useEffect(() => {
|
||||||
|
document.body.classList.add('focalboard-body')
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
if (root) {
|
||||||
|
while (root.firstElementChild) {
|
||||||
|
if( root.firstElementChild.id === 'focalboard-app'){
|
||||||
|
break
|
||||||
|
}
|
||||||
|
root.removeChild(root.firstElementChild)
|
||||||
|
}
|
||||||
|
root.classList.add('focalboard-plugin-root')
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.body.classList.remove('focalboard-body')
|
||||||
|
if (root) {
|
||||||
|
root.classList.remove('focalboard-plugin-root')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<div id='focalboard-app'>
|
||||||
|
<PublicApp />
|
||||||
|
</div>
|
||||||
|
<div id='focalboard-root-portal' />
|
||||||
|
</ReduxProvider>
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const HeaderComponent = () => {
|
const HeaderComponent = () => {
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<GlobalHeader history={browserHistory}/>
|
<GlobalHeader history={browserHistory} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -243,8 +278,6 @@ export default class Plugin {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const initCurrentChannelId = mmStore.getState().entities.channels.currentChannelId
|
const initCurrentChannelId = mmStore.getState().entities.channels.currentChannelId
|
||||||
const initCurrentChannel = mmStore.getState().entities.channels.channels[initCurrentChannelId]
|
const initCurrentChannel = mmStore.getState().entities.channels.channels[initCurrentChannelId]
|
||||||
let lastViewedChannelId = initCurrentChannelId
|
let lastViewedChannelId = initCurrentChannelId
|
||||||
@@ -274,7 +307,9 @@ export default class Plugin {
|
|||||||
prevTeamId = currentTeamId
|
prevTeamId = currentTeamId
|
||||||
store.dispatch(setTeam(currentTeamId))
|
store.dispatch(setTeam(currentTeamId))
|
||||||
octoClient.teamId = currentTeamId
|
octoClient.teamId = currentTeamId
|
||||||
store.dispatch(initialLoad())
|
if(!window.location.pathname.includes(publicBaseURL())){
|
||||||
|
store.dispatch(initialLoad())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTeamId && currentTeamId !== prevTeamId) {
|
if (currentTeamId && currentTeamId !== prevTeamId) {
|
||||||
@@ -336,6 +371,9 @@ export default class Plugin {
|
|||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
() => null,
|
() => null,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
PublicMainApp,
|
||||||
)
|
)
|
||||||
|
|
||||||
const goToFocalboardTemplate = () => {
|
const goToFocalboardTemplate = () => {
|
||||||
|
|||||||
61
webapp/boards/src/public/app.tsx
Обычный файл
61
webapp/boards/src/public/app.tsx
Обычный файл
@@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import React from 'react'
|
||||||
|
import {Router, Switch} from 'react-router-dom'
|
||||||
|
|
||||||
|
import {IntlProvider} from 'react-intl'
|
||||||
|
import {DndProvider} from 'react-dnd'
|
||||||
|
import {HTML5Backend} from 'react-dnd-html5-backend'
|
||||||
|
import {TouchBackend} from 'react-dnd-touch-backend'
|
||||||
|
import {createBrowserHistory} from 'history'
|
||||||
|
|
||||||
|
import BoardPage from 'src/pages/boardPage/boardPage'
|
||||||
|
import FBRoute from 'src/route'
|
||||||
|
|
||||||
|
import {getMessages} from 'src/i18n'
|
||||||
|
import FlashMessages from 'src/components/flashMessages'
|
||||||
|
import NewVersionBanner from 'src/components/newVersionBanner'
|
||||||
|
import {Utils} from 'src/utils'
|
||||||
|
import {getLanguage} from 'src/store/language'
|
||||||
|
import {useAppSelector} from 'src/store/hooks'
|
||||||
|
|
||||||
|
export const publicBaseURL = () => {
|
||||||
|
return Utils.getFrontendBaseURL() + '/public'
|
||||||
|
}
|
||||||
|
|
||||||
|
const PublicRouter = () => {
|
||||||
|
const history = createBrowserHistory({basename: publicBaseURL()})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router history={history}>
|
||||||
|
<Switch>
|
||||||
|
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
|
||||||
|
<BoardPage readonly={true}/>
|
||||||
|
</FBRoute>
|
||||||
|
</Switch>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PublicApp = (): JSX.Element => {
|
||||||
|
const language = useAppSelector<string>(getLanguage)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IntlProvider
|
||||||
|
locale={language.split(/[_]/)[0]}
|
||||||
|
messages={getMessages(language)}
|
||||||
|
>
|
||||||
|
<DndProvider backend={Utils.isMobile() ? TouchBackend : HTML5Backend}>
|
||||||
|
<FlashMessages milliseconds={2000}/>
|
||||||
|
<div id='frame'>
|
||||||
|
<div id='main'>
|
||||||
|
<NewVersionBanner/>
|
||||||
|
<PublicRouter/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DndProvider>
|
||||||
|
</IntlProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(PublicApp)
|
||||||
4
webapp/boards/src/types/mattermost-webapp/index.d.ts
поставляемый
4
webapp/boards/src/types/mattermost-webapp/index.d.ts
поставляемый
@@ -14,7 +14,9 @@ export interface PluginRegistry {
|
|||||||
registerCustomRoute(route: string, component: ReactResolvable): any
|
registerCustomRoute(route: string, component: ReactResolvable): any
|
||||||
registerProductRoute(route: string, component: ReactResolvable): any
|
registerProductRoute(route: string, component: ReactResolvable): any
|
||||||
unregisterComponent(componentId: string): any
|
unregisterComponent(componentId: string): any
|
||||||
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable, showTeamSidebar: boolean): any
|
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string,
|
||||||
|
mainComponent: ReactResolvable, headerCentreComponent: ReactResolvable, headerRightComponent: ReactResolvable,
|
||||||
|
showTeamSidebar: boolean, showAppBar: boolean, wrapped: boolean, publicComponent: ReactResolvable | null): any
|
||||||
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean): any
|
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean): any
|
||||||
registerWebSocketEventHandler(event: string, handler: (e: any) => void): any
|
registerWebSocketEventHandler(event: string, handler: (e: any) => void): any
|
||||||
unregisterWebSocketEventHandler(event: string): any
|
unregisterWebSocketEventHandler(event: string): any
|
||||||
|
|||||||
@@ -5746,7 +5746,6 @@ const AdminDefinition = {
|
|||||||
sectionTitle: t('admin.sidebar.products'),
|
sectionTitle: t('admin.sidebar.products'),
|
||||||
sectionTitleDefault: 'Products',
|
sectionTitleDefault: 'Products',
|
||||||
isHidden: it.any(
|
isHidden: it.any(
|
||||||
it.configIsFalse('FeatureFlags', 'BoardsProduct'),
|
|
||||||
it.not(it.userHasReadPermissionOnSomeResources(RESOURCE_KEYS.PRODUCTS)),
|
it.not(it.userHasReadPermissionOnSomeResources(RESOURCE_KEYS.PRODUCTS)),
|
||||||
),
|
),
|
||||||
boards: {
|
boards: {
|
||||||
|
|||||||
@@ -1072,6 +1072,37 @@ exports[`components/AdminSidebar should match snapshot 1`] = `
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
icon={
|
||||||
@@ -1775,6 +1806,37 @@ exports[`components/AdminSidebar should match snapshot with workspace optimizati
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
icon={
|
||||||
@@ -2533,6 +2595,37 @@ exports[`components/AdminSidebar should match snapshot, not prevent the console
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
icon={
|
||||||
@@ -3236,6 +3329,37 @@ exports[`components/AdminSidebar should match snapshot, render plugins without a
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
icon={
|
||||||
@@ -4038,6 +4162,37 @@ exports[`components/AdminSidebar should match snapshot, with license (with all f
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
icon={
|
||||||
@@ -4973,6 +5128,37 @@ exports[`components/AdminSidebar should match snapshot, with license (without an
|
|||||||
title="Plugin 0"
|
title="Plugin 0"
|
||||||
/>
|
/>
|
||||||
</AdminSidebarCategory>
|
</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
|
<AdminSidebarCategory
|
||||||
definitionKey="integrations"
|
definitionKey="integrations"
|
||||||
icon={
|
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 * as GlobalActions from 'actions/global_actions';
|
||||||
import Constants, {StoragePrefixes, WindowSizes} from 'utils/constants';
|
import Constants, {StoragePrefixes, WindowSizes} from 'utils/constants';
|
||||||
import matchMedia from 'tests/helpers/match_media.mock';
|
import matchMedia from 'tests/helpers/match_media.mock';
|
||||||
|
import {ProductComponent} from 'types/store/plugins';
|
||||||
|
|
||||||
jest.mock('rudder-sdk-js', () => ({
|
jest.mock('rudder-sdk-js', () => ({
|
||||||
identify: jest.fn(),
|
identify: jest.fn(),
|
||||||
@@ -64,7 +65,7 @@ describe('components/Root', () => {
|
|||||||
registerCustomPostRenderer: jest.fn(),
|
registerCustomPostRenderer: jest.fn(),
|
||||||
initializeProducts: jest.fn(),
|
initializeProducts: jest.fn(),
|
||||||
},
|
},
|
||||||
permalinkRedirectTeamName: '',
|
permalinkRedirectTeamName: 'myTeam',
|
||||||
showLaunchingWorkspace: false,
|
showLaunchingWorkspace: false,
|
||||||
plugins: [],
|
plugins: [],
|
||||||
products: [],
|
products: [],
|
||||||
@@ -325,4 +326,33 @@ describe('components/Root', () => {
|
|||||||
wrapper.unmount();
|
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/>
|
<TeamSidebar/>
|
||||||
<DelinquencyModalController/>
|
<DelinquencyModalController/>
|
||||||
<Switch>
|
<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) => (
|
{this.props.products?.map((product) => (
|
||||||
<Route
|
<Route
|
||||||
key={product.id}
|
key={product.id}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ type Props = {
|
|||||||
*
|
*
|
||||||
* Only supported when pluggableName is "Product".
|
* 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
|
* Accept any other prop to pass onto the plugin component
|
||||||
|
|||||||
@@ -986,6 +986,7 @@ export default class PluginRegistry {
|
|||||||
'showTeamSidebar',
|
'showTeamSidebar',
|
||||||
'showAppBar',
|
'showAppBar',
|
||||||
'wrapped',
|
'wrapped',
|
||||||
|
'publicComponent',
|
||||||
], ({
|
], ({
|
||||||
baseURL,
|
baseURL,
|
||||||
switcherIcon,
|
switcherIcon,
|
||||||
@@ -997,6 +998,7 @@ export default class PluginRegistry {
|
|||||||
showTeamSidebar = false,
|
showTeamSidebar = false,
|
||||||
showAppBar = false,
|
showAppBar = false,
|
||||||
wrapped = true,
|
wrapped = true,
|
||||||
|
publicComponent,
|
||||||
}: Omit<ProductComponent, 'id' | 'pluginId'>) => {
|
}: Omit<ProductComponent, 'id' | 'pluginId'>) => {
|
||||||
const id = generateId();
|
const id = generateId();
|
||||||
|
|
||||||
@@ -1016,6 +1018,7 @@ export default class PluginRegistry {
|
|||||||
showTeamSidebar,
|
showTeamSidebar,
|
||||||
showAppBar,
|
showAppBar,
|
||||||
wrapped,
|
wrapped,
|
||||||
|
publicComponent,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,11 @@ export type ProductComponent = {
|
|||||||
*/
|
*/
|
||||||
mainComponent: React.ComponentType;
|
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
|
* A component to fill the generic area in the center of
|
||||||
* the global header when your route is active.
|
* the global header when your route is active.
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ export class TestHelper {
|
|||||||
showTeamSidebar: false,
|
showTeamSidebar: false,
|
||||||
showAppBar: false,
|
showAppBar: false,
|
||||||
wrapped: true,
|
wrapped: true,
|
||||||
|
publicComponent: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,8 @@ export default class Plugin {
|
|||||||
BackstageWrapped,
|
BackstageWrapped,
|
||||||
GlobalHeaderCenter,
|
GlobalHeaderCenter,
|
||||||
GlobalHeaderRight,
|
GlobalHeaderRight,
|
||||||
enableTeamSidebar
|
enableTeamSidebar,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
// RHS Registration
|
// RHS Registration
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user