Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MI-3835] Add logic to hide/show RHS on the basis of system console setting #44

Merged
merged 9 commits into from
Dec 14, 2023
6 changes: 6 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@
"value": "UserPrincipalName"
}
]
},{
"key": "enableRhs",
"display_name": "Enable RHS",
"type": "bool",
"help_text": "When true, users will be able to view RHS (right hand sidebar).",
"default": false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this confirmed from Mike that initially don't show to anyone?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

},{
"key": "appManifestDownload",
"display_name": "Download Manifest",
Expand Down
14 changes: 3 additions & 11 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func NewAPI(p *Plugin, store store.Store) *API {
router.HandleFunc("/oauth-redirect", api.oauthRedirectHandler).Methods(http.MethodGet)
router.HandleFunc("/connected-users", api.getConnectedUsers).Methods(http.MethodGet)
router.HandleFunc("/connected-users/download", api.getConnectedUsersFile).Methods(http.MethodGet)
router.HandleFunc("/whitelist-user", api.handleAuthRequired(api.whitelistUser)).Methods(http.MethodGet)
router.HandleFunc("/config", api.handleAuthRequired(api.getConfig)).Methods(http.MethodGet)

channelsRouter.HandleFunc("/link", api.handleAuthRequired(api.checkUserConnected(api.linkChannels))).Methods(http.MethodPost)
channelsRouter.HandleFunc(fmt.Sprintf("/{%s}/unlink", PathParamChannelID), api.handleAuthRequired(api.unlinkChannels)).Methods(http.MethodDelete)
Expand Down Expand Up @@ -892,17 +892,9 @@ func (a *API) getConnectedUsersFile(w http.ResponseWriter, r *http.Request) {
}
}

func (a *API) whitelistUser(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get(HeaderMattermostUserID)
presentInWhitelist, err := a.p.store.IsUserPresentInWhitelist(userID)
if err != nil {
a.p.API.LogError("Error in checking if a user is present in whitelist", "UserID", userID, "Error", err.Error())
http.Error(w, "error in checking if a user is present in whitelist", http.StatusInternalServerError)
return
}

func (a *API) getConfig(w http.ResponseWriter, _ *http.Request) {
response := map[string]bool{
"presentInWhitelist": presentInWhitelist,
"rhsEnabled": a.p.getConfiguration().EnableRHS,
}

w.Header().Set("Content-Type", "application/json")
Expand Down
50 changes: 11 additions & 39 deletions server/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1813,64 +1813,36 @@ func TestUnlinkChannels(t *testing.T) {
}
}

func TestWhitelistUser(t *testing.T) {
func TestGetConfig(t *testing.T) {
for _, test := range []struct {
Name string
SetupPlugin func(*plugintest.API)
SetupStore func(*storemocks.Store)
SetupMetrics func(*metricsmocks.Metrics)
SetupPlugin func(*Plugin)
ExpectedResult string
ExpectedStatusCode int
}{
{
Name: "WhitelistUser: unable to check user in the whitelist",
SetupPlugin: func(api *plugintest.API) {
api.On("LogError", "Error in checking if a user is present in whitelist", "UserID", testutils.GetUserID(), "Error", "unable to check user in the whitelist").Times(1)
},
SetupStore: func(store *storemocks.Store) {
store.On("IsUserPresentInWhitelist", testutils.GetUserID()).Return(false, errors.New("unable to check user in the whitelist")).Times(1)
},
SetupMetrics: func(mockmetrics *metricsmocks.Metrics) {
mockmetrics.On("IncrementHTTPErrors").Times(1)
Name: "RhsEnabled: rhs is enabled",
SetupPlugin: func(p *Plugin) {
p.configuration.EnableRHS = true
},
ExpectedResult: "error in checking if a user is present in whitelist\n",
ExpectedStatusCode: http.StatusInternalServerError,
},
{
Name: "WhitelistUser: user is not present in whitelist",
SetupPlugin: func(api *plugintest.API) {},
SetupStore: func(store *storemocks.Store) {
store.On("IsUserPresentInWhitelist", testutils.GetUserID()).Return(false, nil).Times(1)
},
SetupMetrics: func(mockmetrics *metricsmocks.Metrics) {},
ExpectedResult: `{"presentInWhitelist":false}`,
ExpectedResult: `{"rhsEnabled":true}`,
ExpectedStatusCode: http.StatusOK,
},
{
Name: "WhitelistUser: user present in whitelist",
SetupPlugin: func(api *plugintest.API) {},
SetupStore: func(store *storemocks.Store) {
store.On("IsUserPresentInWhitelist", testutils.GetUserID()).Return(true, nil).Times(1)
},
SetupMetrics: func(mockmetrics *metricsmocks.Metrics) {},
ExpectedResult: `{"presentInWhitelist":true}`,
Name: "RhsEnabled: rhs is not enabled",
SetupPlugin: func(p *Plugin) {},
ExpectedResult: `{"rhsEnabled":false}`,
ExpectedStatusCode: http.StatusOK,
},
} {
t.Run(test.Name, func(t *testing.T) {
assert := assert.New(t)
plugin := newTestPlugin(t)
mockAPI := &plugintest.API{}

plugin.SetAPI(mockAPI)
defer mockAPI.AssertExpectations(t)

test.SetupPlugin(mockAPI)
test.SetupStore(plugin.store.(*storemocks.Store))
test.SetupMetrics(plugin.metricsService.(*metricsmocks.Metrics))
test.SetupPlugin(plugin)

w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/whitelist-user", nil)
r := httptest.NewRequest(http.MethodGet, "/config", nil)
r.Header.Add(HeaderMattermostUserID, testutils.GetUserID())
plugin.ServeHTTP(nil, w, r)

Expand Down
1 change: 1 addition & 0 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type configuration struct {
SyntheticUserAuthService string `json:"syntheticUserAuthService"`
SyntheticUserAuthData string `json:"syntheticUserAuthData"`
AutomaticallyPromoteSyntheticUsers bool `json:"automaticallyPromoteSyntheticUsers"`
EnableRHS bool `json:"enableRhs"`
}

func (c *configuration) ProcessConfiguration() {
Expand Down
64 changes: 58 additions & 6 deletions webapp/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import React, {useEffect} from 'react';
import {Action, Store} from 'redux';
import {useDispatch} from 'react-redux';

import usePluginApi from 'hooks/usePluginApi';
import {GlobalState} from 'mattermost-redux/types/store';

import {RhsTitle} from 'components';

//global styles
import {pluginApiServiceConfigs} from 'constants/apiService.constant';
import {defaultPage, defaultPerPage, pluginTitle, rhsButtonId} from 'constants/common.constants';
import {iconUrl} from 'constants/illustrations.constants';

import {Rhs} from 'containers';

import useApiRequestCompletionState from 'hooks/useApiRequestCompletionState';
import usePluginApi from 'hooks/usePluginApi';

import {setConnected} from 'reducers/connectedState';
import {defaultPage, defaultPerPage} from 'constants/common.constants';
import {setIsRhsLoading} from 'reducers/spinner';

// global styles
import 'styles/main.scss';

/**
* This is the main App component for the plugin
* @returns {JSX.Element}
*/
const App = (): JSX.Element => {
const App = ({registry, store}:{registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>}): JSX.Element => {
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved
const dispatch = useDispatch();
const {makeApiRequestWithCompletionStatus, getApiState} = usePluginApi();

useEffect(() => {
const linkedChannelsParams: SearchLinkedChannelParams = {page: defaultPage, per_page: defaultPerPage};

makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.whitelistUser.apiServiceName);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.getConfig.apiServiceName);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.needsConnect.apiServiceName);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.getLinkedChannels.apiServiceName, linkedChannelsParams);
}, []);
Expand All @@ -35,6 +42,8 @@ const App = (): JSX.Element => {
dispatch(setIsRhsLoading(isLoading));
}, [isLoading]);

const {data: configData} = getApiState(pluginApiServiceConfigs.getConfig.apiServiceName);

useApiRequestCompletionState({
serviceName: pluginApiServiceConfigs.needsConnect.apiServiceName,
handleSuccess: () => {
Expand All @@ -43,6 +52,49 @@ const App = (): JSX.Element => {
},
});

useApiRequestCompletionState({
serviceName: pluginApiServiceConfigs.getConfig.apiServiceName,
handleSuccess: () => {
const {rhsEnabled} = configData as ConfigResponse;
const rhsButtonData = localStorage.getItem(rhsButtonId);

// Unregister registered components and remove data present in the local storage.
if (rhsButtonData) {
const data = JSON.parse(rhsButtonData);
registry.unregisterComponent(data.headerId);
registry.unregisterComponent(data.appBarId);
localStorage.removeItem(rhsButtonId);
}

// Register the right hand sidebar component, channel header button and app bar if the rhs is enabled.
if (rhsEnabled) {
let appBarId;
const {_, toggleRHSPlugin} = registry.registerRightHandSidebarComponent(Rhs, <RhsTitle/>);
const headerId = registry.registerChannelHeaderButtonAction(
<img
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved
width={24}
height={24}
src={iconUrl}
style={{filter: 'grayscale(1)'}}
/>, () => store.dispatch(toggleRHSPlugin), null, pluginTitle);

if (registry.registerAppBarComponent) {
ayusht2810 marked this conversation as resolved.
Show resolved Hide resolved
appBarId = registry.registerAppBarComponent(iconUrl, () => store.dispatch(toggleRHSPlugin), pluginTitle);
}

/**
* Store data in local storage to avoid extra registration of the above components.
* This was needed as on the load of the app, the plugin re-registers the above components, and multiple rhs buttons were becoming visible.
* We avoid this by keeping registered component id in local storage, unregistering on the load of the plugin (if registered previously), and registering them again.
*/
localStorage.setItem(rhsButtonId, JSON.stringify({
headerId,
appBarId,
}));
}
},
});

return <></>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,23 @@ import {LinkedChannelCardProps} from './LinkedChannelCard.types';
import './LinkedChannelCard.styles.scss';

export const LinkedChannelCard = ({msTeamsChannelName, msTeamsTeamName, mattermostChannelName, mattermostTeamName}: LinkedChannelCardProps) => (
<div className='px-16 py-12 border-t-1 d-flex gap-4 msteams-linked-channel'>
<div className='msteams-linked-channel__link-icon d-flex align-items-center flex-column justify-center'>
<Icon iconName='link'/>
</div>
<div className='d-flex flex-column gap-6'>
<div className='d-flex gap-8 align-items-center'>
{/* TODO: Update icon on basis of channel type */}
<Icon iconName='globe'/>
<h5 className='my-0'>{mattermostChannelName}</h5>
<h5 className='my-0 opacity-6'>{mattermostTeamName}</h5>
<div className='msteams-sync-utils'>
<div className='px-16 py-12 border-t-1 d-flex gap-4 msteams-linked-channel'>
<div className='msteams-linked-channel__link-icon d-flex align-items-center flex-column justify-center'>
<Icon iconName='link'/>
</div>
<div className='d-flex gap-8 align-items-center'>
<Icon iconName='msTeams'/>
<h5 className='my-0'>{msTeamsChannelName}</h5>
<h5 className='my-0 opacity-6'>{msTeamsTeamName}</h5>
<div className='d-flex flex-column gap-6'>
<div className='d-flex gap-8 align-items-center'>
{/* TODO: Update icon on basis of channel type */}
<Icon iconName='globe'/>
<h5 className='my-0'>{mattermostChannelName}</h5>
<h5 className='my-0 opacity-6'>{mattermostTeamName}</h5>
</div>
<div className='d-flex gap-8 align-items-center'>
<Icon iconName='msTeams'/>
<h5 className='my-0'>{msTeamsChannelName}</h5>
<h5 className='my-0 opacity-6'>{msTeamsTeamName}</h5>
</div>
</div>
</div>
</div>
Expand Down
Loading
Loading