MM-50087_Add marketplace button to apps bar (#22653)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
6c9ec24fb9
Коммит
da7a6825ce
@@ -5,10 +5,10 @@ export function verifyPluginMarketplaceVisibility(shouldBeVisible) {
|
|||||||
cy.uiOpenProductMenu().within(() => {
|
cy.uiOpenProductMenu().within(() => {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
// * Verify Marketplace button should exist
|
// * Verify Marketplace button should exist
|
||||||
cy.findByText('Marketplace').should('exist');
|
cy.findByText('App Marketplace').should('exist');
|
||||||
} else {
|
} else {
|
||||||
// * Verify Marketplace button should not exist
|
// * Verify Marketplace button should not exist
|
||||||
cy.findByText('Marketplace').should('not.exist');
|
cy.findByText('App Marketplace').should('not.exist');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {Client4} from 'mattermost-redux/client';
|
|||||||
import * as Channels from 'mattermost-redux/selectors/entities/channels';
|
import * as Channels from 'mattermost-redux/selectors/entities/channels';
|
||||||
import * as Teams from 'mattermost-redux/selectors/entities/teams';
|
import * as Teams from 'mattermost-redux/selectors/entities/teams';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
import {AppCallResponseTypes} from 'mattermost-redux/constants/apps';
|
import {AppCallResponseTypes} from 'mattermost-redux/constants/apps';
|
||||||
|
|
||||||
import * as GlobalActions from 'actions/global_actions';
|
import * as GlobalActions from 'actions/global_actions';
|
||||||
@@ -55,7 +56,7 @@ const initialState = {
|
|||||||
roles: {
|
roles: {
|
||||||
custom_role: {
|
custom_role: {
|
||||||
permissions: [
|
permissions: [
|
||||||
'sysconsole_read_plugins',
|
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFunc {
|
|||||||
return {data: true};
|
return {data: true};
|
||||||
case '/marketplace':
|
case '/marketplace':
|
||||||
// check if user has permissions to access the read plugins
|
// check if user has permissions to access the read plugins
|
||||||
if (!haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_READ_PLUGINS)) {
|
if (!haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)) {
|
||||||
return {error: {message: localizeMessage('marketplace_command.no_permission', 'You do not have the appropriate permissions to access the marketplace.')}};
|
return {error: {message: localizeMessage('marketplace_command.no_permission', 'You do not have the appropriate permissions to access the marketplace.')}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ export function executeCommand(message: string, args: CommandArgs): ActionFunc {
|
|||||||
return {error: {message: localizeMessage('marketplace_command.disabled', 'The marketplace is disabled. Please contact your System Administrator for details.')}};
|
return {error: {message: localizeMessage('marketplace_command.disabled', 'The marketplace is disabled. Please contact your System Administrator for details.')}};
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(openModal({modalId: ModalIdentifiers.PLUGIN_MARKETPLACE, dialogType: MarketplaceModal}));
|
dispatch(openModal({modalId: ModalIdentifiers.PLUGIN_MARKETPLACE, dialogType: MarketplaceModal, dialogProps: {openedFrom: 'command'}}));
|
||||||
return {data: true};
|
return {data: true};
|
||||||
case '/templates': {
|
case '/templates': {
|
||||||
const workTemplateEnabled = areWorkTemplatesEnabled(state);
|
const workTemplateEnabled = areWorkTemplatesEnabled(state);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`components/actions_menu/ActionsMenu has actions - end user - should not show actions and app marketplace 1`] = `
|
exports[`components/actions_menu/ActionsMenu has actions - marketplace disabled or user not having SYSCONSOLE_WRITE_PLUGINS - should not show actions and app marketplace 1`] = `
|
||||||
<MenuWrapper
|
<MenuWrapper
|
||||||
animationComponent={[Function]}
|
animationComponent={[Function]}
|
||||||
className=""
|
className=""
|
||||||
@@ -68,7 +68,7 @@ exports[`components/actions_menu/ActionsMenu has actions - end user - should not
|
|||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`components/actions_menu/ActionsMenu has actions - sysadmin - should show actions and app marketplace 1`] = `
|
exports[`components/actions_menu/ActionsMenu has actions - marketplace enabled and user has SYSCONSOLE_WRITE_PLUGINS - should show actions and app marketplace 1`] = `
|
||||||
<MenuWrapper
|
<MenuWrapper
|
||||||
animationComponent={[Function]}
|
animationComponent={[Function]}
|
||||||
className=""
|
className=""
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ describe('components/actions_menu/ActionsMenu', () => {
|
|||||||
handleDismissTip: jest.fn(),
|
handleDismissTip: jest.fn(),
|
||||||
showPulsatingDot: false,
|
showPulsatingDot: false,
|
||||||
location: 'center',
|
location: 'center',
|
||||||
|
canOpenMarketplace: false,
|
||||||
actions: {
|
actions: {
|
||||||
openModal: jest.fn(),
|
openModal: jest.fn(),
|
||||||
openAppsModal: jest.fn(),
|
openAppsModal: jest.fn(),
|
||||||
@@ -62,27 +63,29 @@ describe('components/actions_menu/ActionsMenu', () => {
|
|||||||
|
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
pluginMenuItems: dropdownComponents,
|
pluginMenuItems: dropdownComponents,
|
||||||
|
canOpenMarketplace: true,
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(true);
|
expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has actions - sysadmin - should show actions and app marketplace', () => {
|
test('has actions - marketplace enabled and user has SYSCONSOLE_WRITE_PLUGINS - should show actions and app marketplace', () => {
|
||||||
const wrapper = shallowWithIntl(
|
const wrapper = shallowWithIntl(
|
||||||
<ActionsMenu {...baseProps}/>,
|
<ActionsMenu {...baseProps}/>,
|
||||||
);
|
);
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
pluginMenuItems: dropdownComponents,
|
pluginMenuItems: dropdownComponents,
|
||||||
|
canOpenMarketplace: true,
|
||||||
});
|
});
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('has actions - end user - should not show actions and app marketplace', () => {
|
test('has actions - marketplace disabled or user not having SYSCONSOLE_WRITE_PLUGINS - should not show actions and app marketplace', () => {
|
||||||
const wrapper = shallowWithIntl(
|
const wrapper = shallowWithIntl(
|
||||||
<ActionsMenu {...baseProps}/>,
|
<ActionsMenu {...baseProps}/>,
|
||||||
);
|
);
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
pluginMenuItems: dropdownComponents,
|
pluginMenuItems: dropdownComponents,
|
||||||
isSysAdmin: false,
|
canOpenMarketplace: false,
|
||||||
});
|
});
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
@@ -91,6 +94,11 @@ describe('components/actions_menu/ActionsMenu', () => {
|
|||||||
const wrapper = shallowWithIntl(
|
const wrapper = shallowWithIntl(
|
||||||
<ActionsMenu {...baseProps}/>,
|
<ActionsMenu {...baseProps}/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
wrapper.setProps({
|
||||||
|
canOpenMarketplace: true,
|
||||||
|
});
|
||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -116,6 +124,7 @@ describe('components/actions_menu/ActionsMenu', () => {
|
|||||||
components: {
|
components: {
|
||||||
[PLUGGABLE_COMPONENT]: dropdownComponents,
|
[PLUGGABLE_COMPONENT]: dropdownComponents,
|
||||||
},
|
},
|
||||||
|
canOpenMarketplace: true,
|
||||||
});
|
});
|
||||||
expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(true);
|
expect(wrapper.find('#divider_post_post_id_1_marketplace').exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import Permissions from 'mattermost-redux/constants/permissions';
|
|||||||
import {ActionsTutorialTip} from 'components/actions_menu/actions_menu_tutorial_tip';
|
import {ActionsTutorialTip} from 'components/actions_menu/actions_menu_tutorial_tip';
|
||||||
import {ModalData} from 'types/actions';
|
import {ModalData} from 'types/actions';
|
||||||
import MarketplaceModal from 'components/plugin_marketplace';
|
import MarketplaceModal from 'components/plugin_marketplace';
|
||||||
|
import {OpenedFromType} from 'components/plugin_marketplace/marketplace_modal';
|
||||||
import OverlayTrigger from 'components/overlay_trigger';
|
import OverlayTrigger from 'components/overlay_trigger';
|
||||||
import * as PostUtils from 'utils/post_utils';
|
import * as PostUtils from 'utils/post_utils';
|
||||||
import * as Utils from 'utils/utils';
|
import * as Utils from 'utils/utils';
|
||||||
@@ -49,6 +50,7 @@ export type Props = {
|
|||||||
handleDismissTip: () => void;
|
handleDismissTip: () => void;
|
||||||
showPulsatingDot?: boolean;
|
showPulsatingDot?: boolean;
|
||||||
showTutorialTip: boolean;
|
showTutorialTip: boolean;
|
||||||
|
canOpenMarketplace: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Components for overriding provided by plugins
|
* Components for overriding provided by plugins
|
||||||
@@ -145,9 +147,11 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleOpenMarketplace = (): void => {
|
handleOpenMarketplace = (): void => {
|
||||||
|
const openedFrom: OpenedFromType = 'actions_menu';
|
||||||
const openMarketplaceData = {
|
const openMarketplaceData = {
|
||||||
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
||||||
dialogType: MarketplaceModal,
|
dialogType: MarketplaceModal,
|
||||||
|
dialogProps: {openedFrom},
|
||||||
};
|
};
|
||||||
this.props.actions.openModal(openMarketplaceData);
|
this.props.actions.openModal(openMarketplaceData);
|
||||||
};
|
};
|
||||||
@@ -341,7 +345,7 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
|||||||
const {formatMessage} = this.props.intl;
|
const {formatMessage} = this.props.intl;
|
||||||
|
|
||||||
let marketPlace = null;
|
let marketPlace = null;
|
||||||
if (this.props.isSysAdmin) {
|
if (this.props.canOpenMarketplace) {
|
||||||
marketPlace = (
|
marketPlace = (
|
||||||
<React.Fragment key={'marketplace'}>
|
<React.Fragment key={'marketplace'}>
|
||||||
{this.renderDivider('marketplace')}
|
{this.renderDivider('marketplace')}
|
||||||
@@ -363,11 +367,11 @@ export class ActionMenuClass extends React.PureComponent<Props, State> {
|
|||||||
const hasPluginItems = Boolean(pluginItems?.length);
|
const hasPluginItems = Boolean(pluginItems?.length);
|
||||||
|
|
||||||
const hasPluginMenuItems = hasPluginItems || hasApps || hasPluggables;
|
const hasPluginMenuItems = hasPluginItems || hasApps || hasPluggables;
|
||||||
if (!this.props.isSysAdmin && !hasPluginMenuItems) {
|
if (!this.props.canOpenMarketplace && !hasPluginMenuItems) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPluginItems || hasApps || hasPluggables) {
|
if (hasPluginMenuItems) {
|
||||||
const pluggable = (
|
const pluggable = (
|
||||||
<Pluggable
|
<Pluggable
|
||||||
postId={this.props.post.id}
|
postId={this.props.post.id}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ describe('components/actions_menu/ActionsMenu returning empty ("")', () => {
|
|||||||
showTutorialTip: false,
|
showTutorialTip: false,
|
||||||
appsEnabled: false,
|
appsEnabled: false,
|
||||||
isSysAdmin: true,
|
isSysAdmin: true,
|
||||||
|
canOpenMarketplace: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ describe('components/actions_menu/ActionsMenu on mobile view', () => {
|
|||||||
showTutorialTip: false,
|
showTutorialTip: false,
|
||||||
appsEnabled: false,
|
appsEnabled: false,
|
||||||
isSysAdmin: true,
|
isSysAdmin: true,
|
||||||
|
canOpenMarketplace: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import {GlobalState} from 'types/store';
|
|||||||
import {openModal} from 'actions/views/modals';
|
import {openModal} from 'actions/views/modals';
|
||||||
import {makeFetchBindings, postEphemeralCallResponseForPost, handleBindingClick, openAppsModal} from 'actions/apps';
|
import {makeFetchBindings, postEphemeralCallResponseForPost, handleBindingClick, openAppsModal} from 'actions/apps';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
import {isMarketplaceEnabled} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {haveICurrentTeamPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||||
|
|
||||||
import ActionsMenu from './actions_menu';
|
import ActionsMenu from './actions_menu';
|
||||||
import {makeGetPostOptionBinding} from './selectors';
|
import {makeGetPostOptionBinding} from './selectors';
|
||||||
|
|
||||||
@@ -65,6 +69,10 @@ function mapStateToProps(state: GlobalState, ownProps: Props) {
|
|||||||
pluginMenuItems: state.plugins.components.PostDropdownMenu,
|
pluginMenuItems: state.plugins.components.PostDropdownMenu,
|
||||||
teamId: getCurrentTeamId(state),
|
teamId: getCurrentTeamId(state),
|
||||||
isMobileView: getIsMobileView(state),
|
isMobileView: getIsMobileView(state),
|
||||||
|
canOpenMarketplace: (
|
||||||
|
isMarketplaceEnabled(state) &&
|
||||||
|
haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +1,38 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
|
exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
|
||||||
.c0:last-child,
|
|
||||||
.c0:first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
<AppBar>
|
<AppBar>
|
||||||
<div
|
<div
|
||||||
className="app-bar"
|
className="app-bar"
|
||||||
>
|
>
|
||||||
<AppBarPluginComponent
|
<div
|
||||||
component={
|
className="app-bar__top"
|
||||||
Object {
|
|
||||||
"action": [MockFunction],
|
|
||||||
"icon": "fallback_component",
|
|
||||||
"id": "the_component_id",
|
|
||||||
"pluginId": "playbooks",
|
|
||||||
"tooltipText": "Playbooks Tooltip",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
key="the_component_id"
|
|
||||||
>
|
>
|
||||||
<OverlayTrigger
|
<AppBarPluginComponent
|
||||||
defaultOverlayShown={false}
|
component={
|
||||||
delayShow={400}
|
Object {
|
||||||
overlay={
|
"action": [MockFunction],
|
||||||
<Tooltip
|
"icon": "fallback_component",
|
||||||
bsClass="tooltip"
|
"id": "the_component_id",
|
||||||
id="pluginTooltip-app-bar-icon-playbooks"
|
"pluginId": "playbooks",
|
||||||
placement="right"
|
"tooltipText": "Playbooks Tooltip",
|
||||||
>
|
}
|
||||||
<span>
|
|
||||||
Playbooks Tooltip
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="left"
|
|
||||||
trigger={
|
|
||||||
Array [
|
|
||||||
"hover",
|
|
||||||
"focus",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
key="the_component_id"
|
||||||
>
|
>
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
defaultOverlayShown={false}
|
defaultOverlayShown={false}
|
||||||
delayShow={400}
|
delayShow={400}
|
||||||
overlay={
|
overlay={
|
||||||
<OverlayWrapper
|
<Tooltip
|
||||||
bsClass="tooltip"
|
bsClass="tooltip"
|
||||||
id="pluginTooltip-app-bar-icon-playbooks"
|
id="pluginTooltip-app-bar-icon-playbooks"
|
||||||
intl={null}
|
|
||||||
placement="right"
|
placement="right"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Playbooks Tooltip
|
Playbooks Tooltip
|
||||||
</span>
|
</span>
|
||||||
</OverlayWrapper>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
placement="left"
|
placement="left"
|
||||||
trigger={
|
trigger={
|
||||||
@@ -67,77 +42,73 @@ exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<OverlayTrigger
|
||||||
className="app-bar__icon"
|
defaultOverlayShown={false}
|
||||||
id="app-bar-icon-playbooks"
|
delayShow={400}
|
||||||
onBlur={[Function]}
|
overlay={
|
||||||
onClick={[Function]}
|
<OverlayWrapper
|
||||||
onFocus={[Function]}
|
bsClass="tooltip"
|
||||||
onMouseOut={[Function]}
|
id="pluginTooltip-app-bar-icon-playbooks"
|
||||||
onMouseOver={[Function]}
|
intl={null}
|
||||||
|
placement="right"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Playbooks Tooltip
|
||||||
|
</span>
|
||||||
|
</OverlayWrapper>
|
||||||
|
}
|
||||||
|
placement="left"
|
||||||
|
trigger={
|
||||||
|
Array [
|
||||||
|
"hover",
|
||||||
|
"focus",
|
||||||
|
]
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
className="app-bar__icon"
|
||||||
|
id="app-bar-icon-playbooks"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onClick={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onMouseOut={[Function]}
|
||||||
|
onMouseOver={[Function]}
|
||||||
>
|
>
|
||||||
fallback_component
|
<div
|
||||||
|
className="app-bar__old-icon app-bar__icon-inner app-bar__icon-inner--centered"
|
||||||
|
>
|
||||||
|
fallback_component
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</OverlayTrigger>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</OverlayTrigger>
|
</AppBarPluginComponent>
|
||||||
</AppBarPluginComponent>
|
|
||||||
<_StyledHr
|
|
||||||
className="app-bar__divider"
|
|
||||||
key="divider"
|
|
||||||
>
|
|
||||||
<hr
|
<hr
|
||||||
className="c0 app-bar__divider"
|
className="app-bar__divider"
|
||||||
|
key="divider"
|
||||||
/>
|
/>
|
||||||
</_StyledHr>
|
<AppBarBinding
|
||||||
<AppBarBinding
|
binding={
|
||||||
binding={
|
Object {
|
||||||
Object {
|
"app_id": "com.mattermost.zendesk",
|
||||||
"app_id": "com.mattermost.zendesk",
|
"label": "Create Subscription",
|
||||||
"label": "Create Subscription",
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
key="com.mattermost.zendesk_Create Subscription"
|
|
||||||
>
|
|
||||||
<OverlayTrigger
|
|
||||||
defaultOverlayShown={false}
|
|
||||||
delayShow={400}
|
|
||||||
overlay={
|
|
||||||
<Tooltip
|
|
||||||
bsClass="tooltip"
|
|
||||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
|
||||||
placement="right"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Create Subscription
|
|
||||||
</span>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
placement="left"
|
|
||||||
trigger={
|
|
||||||
Array [
|
|
||||||
"hover",
|
|
||||||
"focus",
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
key="com.mattermost.zendesk_Create Subscription"
|
||||||
>
|
>
|
||||||
<OverlayTrigger
|
<OverlayTrigger
|
||||||
defaultOverlayShown={false}
|
defaultOverlayShown={false}
|
||||||
delayShow={400}
|
delayShow={400}
|
||||||
overlay={
|
overlay={
|
||||||
<OverlayWrapper
|
<Tooltip
|
||||||
bsClass="tooltip"
|
bsClass="tooltip"
|
||||||
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||||
intl={null}
|
|
||||||
placement="right"
|
placement="right"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
Create Subscription
|
Create Subscription
|
||||||
</span>
|
</span>
|
||||||
</OverlayWrapper>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
placement="left"
|
placement="left"
|
||||||
trigger={
|
trigger={
|
||||||
@@ -147,25 +118,49 @@ exports[`components/app_bar/app_bar should match snapshot on mount 1`] = `
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<OverlayTrigger
|
||||||
aria-label="Create Subscription"
|
defaultOverlayShown={false}
|
||||||
className="app-bar__icon"
|
delayShow={400}
|
||||||
id="app-bar-icon-com.mattermost.zendesk"
|
overlay={
|
||||||
onBlur={[Function]}
|
<OverlayWrapper
|
||||||
onClick={[Function]}
|
bsClass="tooltip"
|
||||||
onFocus={[Function]}
|
id="tooltip-app-bar-icon-com.mattermost.zendesk"
|
||||||
onMouseOut={[Function]}
|
intl={null}
|
||||||
onMouseOver={[Function]}
|
placement="right"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Create Subscription
|
||||||
|
</span>
|
||||||
|
</OverlayWrapper>
|
||||||
|
}
|
||||||
|
placement="left"
|
||||||
|
trigger={
|
||||||
|
Array [
|
||||||
|
"hover",
|
||||||
|
"focus",
|
||||||
|
]
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="app-bar__icon-inner"
|
aria-label="Create Subscription"
|
||||||
|
className="app-bar__icon"
|
||||||
|
id="app-bar-icon-com.mattermost.zendesk"
|
||||||
|
onBlur={[Function]}
|
||||||
|
onClick={[Function]}
|
||||||
|
onFocus={[Function]}
|
||||||
|
onMouseOut={[Function]}
|
||||||
|
onMouseOver={[Function]}
|
||||||
>
|
>
|
||||||
<img />
|
<div
|
||||||
|
className="app-bar__icon-inner"
|
||||||
|
>
|
||||||
|
<img />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</OverlayTrigger>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</OverlayTrigger>
|
</AppBarBinding>
|
||||||
</AppBarBinding>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
@import 'utils/mixins';
|
||||||
|
|
||||||
$app-bar-icon-size: 24px;
|
$app-bar-icon-size: 24px;
|
||||||
$app-bar-width: 48px;
|
$app-bar-width: 48px;
|
||||||
|
|
||||||
@@ -10,129 +12,143 @@ $app-bar-width: 48px;
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
position: relative;
|
display: flex;
|
||||||
width: $app-bar-width;
|
min-height: 0;
|
||||||
padding-top: 16px;
|
flex-flow: column;
|
||||||
|
border-left: solid 1px rgba(var(--center-channel-color-rgb), 0.12);
|
||||||
background-color: var(--center-channel-bg);
|
background-color: var(--center-channel-bg);
|
||||||
-ms-overflow-style: none;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: scroll;
|
|
||||||
scrollbar-width: none;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&::before {
|
&__top {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-left: solid 1px rgba(var(--center-channel-color-rgb), 0.12);
|
|
||||||
background-color: rgba(var(--center-channel-color-rgb), 0.04);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-bar__icon {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
// Render App Bar icons on top of the RHS background div
|
display: flex;
|
||||||
//(see `@media screen and (min-width: 769px) > #sidebar-right` in sass/layout/_sidebar-right.scss)
|
width: $app-bar-width;
|
||||||
z-index: 21;
|
flex: 1;
|
||||||
width: 100%;
|
flex-flow: column;
|
||||||
border-left: none;
|
padding-top: 16px;
|
||||||
margin-bottom: 16px;
|
background-color: rgba(var(--center-channel-color-rgb), 0.04);
|
||||||
cursor: pointer;
|
-ms-overflow-style: none;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
&--active {
|
.app-bar__icon {
|
||||||
&::before {
|
position: relative;
|
||||||
position: absolute;
|
// Render App Bar icons on top of the RHS background div
|
||||||
top: 0;
|
//(see `@media screen and (min-width: 769px) > #sidebar-right` in sass/layout/_sidebar-right.scss)
|
||||||
left: 0;
|
z-index: 21;
|
||||||
width: 3px;
|
width: 100%;
|
||||||
height: $app-bar-icon-size;
|
border-left: none;
|
||||||
background-color: var(--sidebar-text-active-border);
|
margin-bottom: 16px;
|
||||||
border-radius: 0 2px 2px 0;
|
cursor: pointer;
|
||||||
content: '';
|
|
||||||
|
&--active {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 3px;
|
||||||
|
height: $app-bar-icon-size;
|
||||||
|
background-color: var(--sidebar-text-active-border);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-bar__icon-inner,
|
||||||
|
span:not(.pulsating_dot) {
|
||||||
|
// if we want to show a tourtip/pulsating dot in any of the app bar icons, these styles must be ommitted when span.pulsating_dot
|
||||||
|
box-shadow: 0 0 0 2px var(--sidebar-text-active-border);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 0 2px rgba(var(--sidebar-text-active-border-rgb), 0.92) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-bar__icon-inner,
|
.app-bar__icon-inner,
|
||||||
span:not(.pulsating_dot) {
|
span:not(.pulsating_dot) {
|
||||||
// if we want to show a tourtip/pulsating dot in any of the app bar icons, these styles must be ommitted when span.pulsating_dot
|
display: block;
|
||||||
box-shadow: 0 0 0 2px var(--sidebar-text-active-border);
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--sidebar-text-active-border-rgb), 0.92) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-bar__icon-inner,
|
|
||||||
span:not(.pulsating_dot) {
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
width: $app-bar-icon-size;
|
|
||||||
height: $app-bar-icon-size;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: 50%;
|
|
||||||
line-height: 1;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--center-channel-color-rgb), 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: $app-bar-icon-size;
|
width: $app-bar-icon-size;
|
||||||
height: $app-bar-icon-size;
|
height: $app-bar-icon-size;
|
||||||
|
margin: 0 auto;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
line-height: 1;
|
||||||
}
|
|
||||||
|
|
||||||
span:not(.pulsating_dot) {
|
&:hover {
|
||||||
padding: 2px;
|
box-shadow: 0 0 0 2px rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
background-color: white;
|
}
|
||||||
fill: var(--button-bg);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
vertical-align: middle;
|
|
||||||
|
|
||||||
&.CompassIcon,
|
img {
|
||||||
&.icon-brand-zoom {
|
width: $app-bar-icon-size;
|
||||||
font-size: 20px;
|
height: $app-bar-icon-size;
|
||||||
|
border-radius: 50%;
|
||||||
&::before {
|
|
||||||
margin: 0 0 0 0.5px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.app-bar__old-icon {
|
span:not(.pulsating_dot) {
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.56);
|
padding: 2px;
|
||||||
|
background-color: white;
|
||||||
|
fill: var(--button-bg);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
&:hover,
|
&.CompassIcon,
|
||||||
&--active {
|
&.icon-brand-zoom {
|
||||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
font-size: 20px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
margin: 0 0 0 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-bar__old-icon {
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&--active {
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-bar__icon-inner--centered {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-bar__icon-inner--centered {
|
.app-bar__divider {
|
||||||
display: grid;
|
width: 28px;
|
||||||
place-items: center;
|
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||||
|
margin-top: 14px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-bar__icon.channel-header__icon--active {
|
||||||
|
background: rgba(var(--button-bg-rgb), 0.08);
|
||||||
|
color: var(--button-bg);
|
||||||
|
fill: var(--button-bg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-bar__divider {
|
&__bottom {
|
||||||
width: 28px;
|
display: flex;
|
||||||
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
flex-flow: column;
|
||||||
margin-top: 14px;
|
align-items: center;
|
||||||
margin-bottom: 14px;
|
padding-top: 24px;
|
||||||
}
|
padding-bottom: 36px;
|
||||||
|
background-color: rgba(var(--center-channel-color-rgb), 0.04);
|
||||||
|
|
||||||
.app-bar__icon.channel-header__icon--active {
|
.app_bar__marketplace_button {
|
||||||
background: rgba(var(--button-bg-rgb), 0.08);
|
@include icon-button;
|
||||||
color: var(--button-bg);
|
@include icon-button-small-compact;
|
||||||
fill: var(--button-bg);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This style is defined outside the .app-bar block above because it doesn't seem to work when defined there
|
// This style is defined outside the .app-bar block above because it doesn't seem to work when defined there
|
||||||
.app-bar::-webkit-scrollbar {
|
.app-bar__top::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {mount} from 'enzyme';
|
import {mount, shallow} from 'enzyme';
|
||||||
import 'jest-styled-components';
|
import 'jest-styled-components';
|
||||||
|
|
||||||
import {AppBinding} from '@mattermost/types/apps';
|
import {AppBinding} from '@mattermost/types/apps';
|
||||||
@@ -10,6 +10,7 @@ import {AppBinding} from '@mattermost/types/apps';
|
|||||||
import {PluginComponent} from 'types/store/plugins';
|
import {PluginComponent} from 'types/store/plugins';
|
||||||
import {GlobalState} from 'types/store';
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
import {AppBindingLocations} from 'mattermost-redux/constants/apps';
|
import {AppBindingLocations} from 'mattermost-redux/constants/apps';
|
||||||
|
|
||||||
import AppBar from './app_bar';
|
import AppBar from './app_bar';
|
||||||
@@ -82,6 +83,21 @@ describe('components/app_bar/app_bar', () => {
|
|||||||
myPreferences: {
|
myPreferences: {
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
|
users: {
|
||||||
|
currentUserId: 'user1',
|
||||||
|
profiles: {
|
||||||
|
user1: {
|
||||||
|
roles: 'system_user',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
roles: {
|
||||||
|
roles: {
|
||||||
|
system_user: {
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
},
|
},
|
||||||
} as GlobalState;
|
} as GlobalState;
|
||||||
});
|
});
|
||||||
@@ -134,4 +150,48 @@ describe('components/app_bar/app_bar', () => {
|
|||||||
|
|
||||||
expect(wrapper).toMatchSnapshot();
|
expect(wrapper).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not show marketplace if disabled or user does not have SYSCONSOLE_WRITE_PLUGINS permission', async () => {
|
||||||
|
mockState.entities.general = {
|
||||||
|
config: {
|
||||||
|
EnableAppBar: 'true',
|
||||||
|
FeatureFlagAppsEnabled: 'true',
|
||||||
|
EnableMarketplace: 'true',
|
||||||
|
PluginsEnabled: 'true',
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AppBar/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('AppBarMarketplace').exists()).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show marketplace if enabled and user has SYSCONSOLE_WRITE_PLUGINS permission', async () => {
|
||||||
|
mockState.entities.general = {
|
||||||
|
config: {
|
||||||
|
EnableAppBar: 'true',
|
||||||
|
FeatureFlagAppsEnabled: 'true',
|
||||||
|
EnableMarketplace: 'true',
|
||||||
|
PluginsEnabled: 'true',
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
mockState.entities.roles = {
|
||||||
|
roles: {
|
||||||
|
system_user: {
|
||||||
|
permissions: [
|
||||||
|
Permissions.SYSCONSOLE_WRITE_PLUGINS,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<AppBar/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wrapper.find('AppBarMarketplace').exists()).toEqual(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,8 +12,15 @@ import {getAppBarAppBindings} from 'mattermost-redux/selectors/entities/apps';
|
|||||||
import {getAppBarPluginComponents, getChannelHeaderPluginComponents, shouldShowAppBar} from 'selectors/plugins';
|
import {getAppBarPluginComponents, getChannelHeaderPluginComponents, shouldShowAppBar} from 'selectors/plugins';
|
||||||
import {suitePluginIds} from 'utils/constants';
|
import {suitePluginIds} from 'utils/constants';
|
||||||
|
|
||||||
|
import {Permissions} from 'mattermost-redux/constants';
|
||||||
|
import {isMarketplaceEnabled} from 'mattermost-redux/selectors/entities/general';
|
||||||
|
import {haveICurrentTeamPermission} from 'mattermost-redux/selectors/entities/roles';
|
||||||
|
|
||||||
|
import {GlobalState} from '@mattermost/types/store';
|
||||||
|
|
||||||
import AppBarPluginComponent, {isAppBarPluginComponent} from './app_bar_plugin_component';
|
import AppBarPluginComponent, {isAppBarPluginComponent} from './app_bar_plugin_component';
|
||||||
import AppBarBinding, {isAppBinding} from './app_bar_binding';
|
import AppBarBinding, {isAppBinding} from './app_bar_binding';
|
||||||
|
import AppBarMarketplace from './app_bar_marketplace';
|
||||||
|
|
||||||
import './app_bar.scss';
|
import './app_bar.scss';
|
||||||
|
|
||||||
@@ -24,6 +31,10 @@ export default function AppBar() {
|
|||||||
const currentProduct = useCurrentProduct();
|
const currentProduct = useCurrentProduct();
|
||||||
const currentProductId = useCurrentProductId();
|
const currentProductId = useCurrentProductId();
|
||||||
const enabled = useSelector(shouldShowAppBar);
|
const enabled = useSelector(shouldShowAppBar);
|
||||||
|
const canOpenMarketplace = useSelector((state: GlobalState) => (
|
||||||
|
isMarketplaceEnabled(state) &&
|
||||||
|
haveICurrentTeamPermission(state, Permissions.SYSCONSOLE_WRITE_PLUGINS)
|
||||||
|
));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!enabled ||
|
!enabled ||
|
||||||
@@ -40,11 +51,15 @@ export default function AppBar() {
|
|||||||
|
|
||||||
const items: ReactNode[] = [
|
const items: ReactNode[] = [
|
||||||
...coreProductComponents,
|
...coreProductComponents,
|
||||||
divider,
|
getDivider(coreProductComponents.length, (pluginComponents.length + channelHeaderComponents.length + appBarBindings.length)),
|
||||||
...pluginComponents,
|
...pluginComponents,
|
||||||
...channelHeaderComponents,
|
...channelHeaderComponents,
|
||||||
...appBarBindings,
|
...appBarBindings,
|
||||||
].map((x) => {
|
].map((x) => {
|
||||||
|
if (!x) {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
if (isAppBarPluginComponent(x)) {
|
if (isAppBarPluginComponent(x)) {
|
||||||
if (!inScope(x.supportedProductIds ?? null, currentProductId, currentProduct?.pluginId)) {
|
if (!inScope(x.supportedProductIds ?? null, currentProductId, currentProduct?.pluginId)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -69,26 +84,23 @@ export default function AppBar() {
|
|||||||
return x;
|
return x;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!items.some((x) => Boolean(x) && x !== divider)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'app-bar'}>
|
<div className={'app-bar'}>
|
||||||
{items}
|
<div className={'app-bar__top'}>
|
||||||
|
{items}
|
||||||
|
</div>
|
||||||
|
{canOpenMarketplace && (
|
||||||
|
<div className='app-bar__bottom'>
|
||||||
|
<AppBarMarketplace/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const divider = (
|
const getDivider = (beforeCount: number, afterCount: number) => (beforeCount && afterCount ? (
|
||||||
<hr
|
<hr
|
||||||
key='divider'
|
key='divider'
|
||||||
className={'app-bar__divider'}
|
className='app-bar__divider'
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
|
||||||
css={`
|
|
||||||
:last-child, :first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
/>
|
/>
|
||||||
);
|
) : null);
|
||||||
|
|||||||
60
webapp/channels/src/components/app_bar/app_bar_marketplace.tsx
Обычный файл
60
webapp/channels/src/components/app_bar/app_bar_marketplace.tsx
Обычный файл
@@ -0,0 +1,60 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React, {useCallback} from 'react';
|
||||||
|
import {useDispatch} from 'react-redux';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {Tooltip} from 'react-bootstrap';
|
||||||
|
|
||||||
|
import Icon from '@mattermost/compass-components/foundations/icon';
|
||||||
|
|
||||||
|
import {openModal} from 'actions/views/modals';
|
||||||
|
|
||||||
|
import MarketplaceModal from 'components/plugin_marketplace';
|
||||||
|
import OverlayTrigger from 'components/overlay_trigger';
|
||||||
|
|
||||||
|
import {Constants, ModalIdentifiers} from 'utils/constants';
|
||||||
|
|
||||||
|
const AppBarMarketplace = () => {
|
||||||
|
const {formatMessage} = useIntl();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleOpenMarketplace = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalId: ModalIdentifiers.PLUGIN_MARKETPLACE,
|
||||||
|
dialogType: MarketplaceModal,
|
||||||
|
dialogProps: {openedFrom: 'app_bar'},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const label = formatMessage({id: 'app_bar.marketplace', defaultMessage: 'App Marketplace'});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverlayTrigger
|
||||||
|
trigger={['hover', 'focus']}
|
||||||
|
delayShow={Constants.OVERLAY_TIME_DELAY}
|
||||||
|
placement='left'
|
||||||
|
overlay={(
|
||||||
|
<Tooltip id='tooltip-app-bar-marketplace'>
|
||||||
|
<span>{label}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
key='app_bar_marketplace'
|
||||||
|
className='app_bar__marketplace_button'
|
||||||
|
aria-label={label}
|
||||||
|
onClick={handleOpenMarketplace}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
size={16}
|
||||||
|
glyph={'view-grid-plus-outline'}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</OverlayTrigger>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppBarMarketplace;
|
||||||
@@ -301,6 +301,7 @@ export default function OpenPluginInstallPost(props: {post: Post}) {
|
|||||||
className='color--link'
|
className='color--link'
|
||||||
modalId={ModalIdentifiers.PLUGIN_MARKETPLACE}
|
modalId={ModalIdentifiers.PLUGIN_MARKETPLACE}
|
||||||
dialogType={MarketplaceModal}
|
dialogType={MarketplaceModal}
|
||||||
|
dialogProps={{openedFrom: 'open_plugin_install_post'}}
|
||||||
>
|
>
|
||||||
{text}
|
{text}
|
||||||
</ToggleModalButton>
|
</ToggleModalButton>
|
||||||
|
|||||||
@@ -147,6 +147,11 @@ exports[`components/global/product_switcher_menu should match snapshot with id 1
|
|||||||
teamId=""
|
teamId=""
|
||||||
>
|
>
|
||||||
<MenuItemToggleModalRedux
|
<MenuItemToggleModalRedux
|
||||||
|
dialogProps={
|
||||||
|
Object {
|
||||||
|
"openedFrom": "product_menu",
|
||||||
|
}
|
||||||
|
}
|
||||||
dialogType={
|
dialogType={
|
||||||
Object {
|
Object {
|
||||||
"$$typeof": Symbol(react.memo),
|
"$$typeof": Symbol(react.memo),
|
||||||
@@ -157,14 +162,14 @@ exports[`components/global/product_switcher_menu should match snapshot with id 1
|
|||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
<Icon
|
<Icon
|
||||||
glyph="apps"
|
glyph="view-grid-plus-outline"
|
||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
id="marketplaceModal"
|
id="marketplaceModal"
|
||||||
modalId="plugin_marketplace"
|
modalId="plugin_marketplace"
|
||||||
show={false}
|
show={false}
|
||||||
text="Marketplace"
|
text="App Marketplace"
|
||||||
/>
|
/>
|
||||||
</Connect(TeamPermissionGate)>
|
</Connect(TeamPermissionGate)>
|
||||||
<MenuItemExternalLink
|
<MenuItemExternalLink
|
||||||
@@ -350,6 +355,11 @@ exports[`components/global/product_switcher_menu should match snapshot with most
|
|||||||
teamId=""
|
teamId=""
|
||||||
>
|
>
|
||||||
<MenuItemToggleModalRedux
|
<MenuItemToggleModalRedux
|
||||||
|
dialogProps={
|
||||||
|
Object {
|
||||||
|
"openedFrom": "product_menu",
|
||||||
|
}
|
||||||
|
}
|
||||||
dialogType={
|
dialogType={
|
||||||
Object {
|
Object {
|
||||||
"$$typeof": Symbol(react.memo),
|
"$$typeof": Symbol(react.memo),
|
||||||
@@ -360,14 +370,14 @@ exports[`components/global/product_switcher_menu should match snapshot with most
|
|||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
<Icon
|
<Icon
|
||||||
glyph="apps"
|
glyph="view-grid-plus-outline"
|
||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
id="marketplaceModal"
|
id="marketplaceModal"
|
||||||
modalId="plugin_marketplace"
|
modalId="plugin_marketplace"
|
||||||
show={true}
|
show={true}
|
||||||
text="Marketplace"
|
text="App Marketplace"
|
||||||
/>
|
/>
|
||||||
</Connect(TeamPermissionGate)>
|
</Connect(TeamPermissionGate)>
|
||||||
<MenuItemExternalLink
|
<MenuItemExternalLink
|
||||||
@@ -673,6 +683,11 @@ exports[`components/global/product_switcher_menu should show integrations should
|
|||||||
teamId=""
|
teamId=""
|
||||||
>
|
>
|
||||||
<MenuItemToggleModalRedux
|
<MenuItemToggleModalRedux
|
||||||
|
dialogProps={
|
||||||
|
Object {
|
||||||
|
"openedFrom": "product_menu",
|
||||||
|
}
|
||||||
|
}
|
||||||
dialogType={
|
dialogType={
|
||||||
Object {
|
Object {
|
||||||
"$$typeof": Symbol(react.memo),
|
"$$typeof": Symbol(react.memo),
|
||||||
@@ -683,14 +698,14 @@ exports[`components/global/product_switcher_menu should show integrations should
|
|||||||
}
|
}
|
||||||
icon={
|
icon={
|
||||||
<Icon
|
<Icon
|
||||||
glyph="apps"
|
glyph="view-grid-plus-outline"
|
||||||
size={16}
|
size={16}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
id="marketplaceModal"
|
id="marketplaceModal"
|
||||||
modalId="plugin_marketplace"
|
modalId="plugin_marketplace"
|
||||||
show={false}
|
show={false}
|
||||||
text="Marketplace"
|
text="App Marketplace"
|
||||||
/>
|
/>
|
||||||
</Connect(TeamPermissionGate)>
|
</Connect(TeamPermissionGate)>
|
||||||
<MenuItemExternalLink
|
<MenuItemExternalLink
|
||||||
|
|||||||
@@ -213,11 +213,12 @@ const ProductMenuList = (props: Props): JSX.Element | null => {
|
|||||||
modalId={ModalIdentifiers.PLUGIN_MARKETPLACE}
|
modalId={ModalIdentifiers.PLUGIN_MARKETPLACE}
|
||||||
show={isMessaging && !isMobile && enablePluginMarketplace}
|
show={isMessaging && !isMobile && enablePluginMarketplace}
|
||||||
dialogType={MarketplaceModal}
|
dialogType={MarketplaceModal}
|
||||||
text={formatMessage({id: 'navbar_dropdown.marketplace', defaultMessage: 'Marketplace'})}
|
dialogProps={{openedFrom: 'product_menu'}}
|
||||||
|
text={formatMessage({id: 'navbar_dropdown.marketplace', defaultMessage: 'App Marketplace'})}
|
||||||
icon={
|
icon={
|
||||||
<Icon
|
<Icon
|
||||||
size={16}
|
size={16}
|
||||||
glyph={'apps'}
|
glyph='view-grid-plus-outline'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ describe('components/marketplace/', () => {
|
|||||||
pluginStatuses: {},
|
pluginStatuses: {},
|
||||||
siteURL: 'http://example.com',
|
siteURL: 'http://example.com',
|
||||||
firstAdminVisitMarketplaceStatus: false,
|
firstAdminVisitMarketplaceStatus: false,
|
||||||
|
openedFrom: 'actions_menu',
|
||||||
actions: {
|
actions: {
|
||||||
closeModal: jest.fn(),
|
closeModal: jest.fn(),
|
||||||
fetchListing: jest.fn(() => {
|
fetchListing: jest.fn(() => {
|
||||||
@@ -191,8 +192,21 @@ describe('components/marketplace/', () => {
|
|||||||
wrapper.setState({filter: 'nps'});
|
wrapper.setState({filter: 'nps'});
|
||||||
wrapper.instance().doSearch();
|
wrapper.instance().doSearch();
|
||||||
|
|
||||||
expect(trackEvent).toHaveBeenCalledWith('plugins', 'ui_marketplace_opened');
|
expect(trackEvent).toHaveBeenCalledWith('plugins', 'ui_marketplace_opened', {from: 'actions_menu'});
|
||||||
expect(trackEvent).toHaveBeenCalledWith('plugins', 'ui_marketplace_search', {filter: 'nps'});
|
expect(trackEvent).toHaveBeenCalledWith('plugins', 'ui_marketplace_search', {filter: 'nps'});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Should call for opened track event on mount', () => {
|
||||||
|
const openedFrom = 'actions_menu';
|
||||||
|
|
||||||
|
shallow<MarketplaceModal>(
|
||||||
|
<MarketplaceModal
|
||||||
|
{...baseProps}
|
||||||
|
openedFrom={openedFrom}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(trackEvent).toHaveBeenCalledWith('plugins', 'ui_marketplace_opened', {from: openedFrom});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ const MarketplaceTabs = {
|
|||||||
|
|
||||||
const SEARCH_TIMEOUT_MILLISECONDS = 200;
|
const SEARCH_TIMEOUT_MILLISECONDS = 200;
|
||||||
|
|
||||||
|
export type OpenedFromType = 'actions_menu' | 'app_bar' | 'channel_header' | 'command' | 'open_plugin_install_post' | 'product_menu';
|
||||||
|
|
||||||
type AllListingProps = {
|
type AllListingProps = {
|
||||||
listing: Array<MarketplacePlugin | MarketplaceApp>;
|
listing: Array<MarketplacePlugin | MarketplaceApp>;
|
||||||
};
|
};
|
||||||
@@ -97,6 +99,7 @@ export type MarketplaceModalProps = {
|
|||||||
siteURL: string;
|
siteURL: string;
|
||||||
pluginStatuses?: Record<string, PluginStatusRedux>;
|
pluginStatuses?: Record<string, PluginStatusRedux>;
|
||||||
firstAdminVisitMarketplaceStatus: boolean;
|
firstAdminVisitMarketplaceStatus: boolean;
|
||||||
|
openedFrom: OpenedFromType;
|
||||||
actions: {
|
actions: {
|
||||||
closeModal: () => void;
|
closeModal: () => void;
|
||||||
fetchListing(localOnly?: boolean): Promise<{error?: Error}>;
|
fetchListing(localOnly?: boolean): Promise<{error?: Error}>;
|
||||||
@@ -131,7 +134,7 @@ export default class MarketplaceModal extends React.PureComponent<MarketplaceMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
trackEvent('plugins', 'ui_marketplace_opened');
|
trackEvent('plugins', 'ui_marketplace_opened', {from: this.props.openedFrom});
|
||||||
|
|
||||||
this.fetchListing();
|
this.fetchListing();
|
||||||
this.props.actions.getPluginStatuses();
|
this.props.actions.getPluginStatuses();
|
||||||
|
|||||||
@@ -2645,6 +2645,7 @@
|
|||||||
"api.team.join_team.post_and_forget": "{username} joined the team.",
|
"api.team.join_team.post_and_forget": "{username} joined the team.",
|
||||||
"api.team.leave.left": "{username} left the team.",
|
"api.team.leave.left": "{username} left the team.",
|
||||||
"api.team.remove_user_from_team.removed": "{removedUsername} was removed from the team.",
|
"api.team.remove_user_from_team.removed": "{removedUsername} was removed from the team.",
|
||||||
|
"app_bar.marketplace": "App Marketplace",
|
||||||
"app.channel.post_update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {old})",
|
"app.channel.post_update_channel_purpose_message.removed": "{username} removed the channel purpose (was: {old})",
|
||||||
"app.channel.post_update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {old} to: {new}",
|
"app.channel.post_update_channel_purpose_message.updated_from": "{username} updated the channel purpose from: {old} to: {new}",
|
||||||
"app.channel.post_update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {new}",
|
"app.channel.post_update_channel_purpose_message.updated_to": "{username} updated the channel purpose to: {new}",
|
||||||
@@ -4222,7 +4223,7 @@
|
|||||||
"navbar_dropdown.logout": "Log Out",
|
"navbar_dropdown.logout": "Log Out",
|
||||||
"navbar_dropdown.manageGroups": "Manage Groups",
|
"navbar_dropdown.manageGroups": "Manage Groups",
|
||||||
"navbar_dropdown.manageMembers": "Manage Members",
|
"navbar_dropdown.manageMembers": "Manage Members",
|
||||||
"navbar_dropdown.marketplace": "Marketplace",
|
"navbar_dropdown.marketplace": "App Marketplace",
|
||||||
"navbar_dropdown.menuAriaLabel": "main menu",
|
"navbar_dropdown.menuAriaLabel": "main menu",
|
||||||
"navbar_dropdown.nativeApps": "Download Apps",
|
"navbar_dropdown.nativeApps": "Download Apps",
|
||||||
"navbar_dropdown.profileSettings": "Profile",
|
"navbar_dropdown.profileSettings": "Profile",
|
||||||
|
|||||||
@@ -262,6 +262,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin icon-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid rgba(var(--denim-button-bg-rgb), 0.32);
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 0;
|
||||||
|
background: rgba(var(--center-channel-color-rgb), 0.08);
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
border: 0;
|
||||||
|
background: rgba(var(--button-bg-rgb), 0.08);
|
||||||
|
color: var(--button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: none;
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.32);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin icon-button-small-compact {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
@mixin simple-in-and-out-after($classPrefix, $transition_time: 300ms) {
|
@mixin simple-in-and-out-after($classPrefix, $transition_time: 300ms) {
|
||||||
.#{$classPrefix}--enter-from-after {
|
.#{$classPrefix}--enter-from-after {
|
||||||
&-enter {
|
&-enter {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user