Skip to content

Commit

Permalink
[MI-3835] Add logic to hide/show RHS on the basis of system console s…
Browse files Browse the repository at this point in the history
…etting (#44)

* [MI-3835] Add logic to hide/show rhs on the presence of user in whitelist

* [MI-3835] Add comments and remove extra imports

* [MI-3835] Fix lint error

* [MI-3835] Minor line addition

* [MI-3835] Add logic to hide/show rhs on the basis of system console setting

* [MI-3835] Fix ci

* [MI-3835] Add more comments

* [MI-3835] Update API
  • Loading branch information
ayusht2810 authored Dec 14, 2023
1 parent 29506d0 commit 20e6f16
Show file tree
Hide file tree
Showing 29 changed files with 1,205 additions and 1,119 deletions.
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
},{
"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 => {
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
width={24}
height={24}
src={iconUrl}
style={{filter: 'grayscale(1)'}}
/>, () => store.dispatch(toggleRHSPlugin), null, pluginTitle);

if (registry.registerAppBarComponent) {
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

0 comments on commit 20e6f16

Please sign in to comment.