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
  • Loading branch information
ayusht2810 committed Dec 13, 2023
1 parent 35cc5a0 commit e3c7514
Show file tree
Hide file tree
Showing 20 changed files with 365 additions and 367 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, user 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("/rhs-enabled", api.handleAuthRequired(api.rhsEnabled)).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) rhsEnabled(w http.ResponseWriter, r *http.Request) {

Check failure on line 895 in server/api.go

View workflow job for this annotation

GitHub Actions / Check style; build; test

unused-parameter: parameter 'r' seems to be unused, consider removing or renaming it as _ (revive)
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 TestRhsEnabled(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, "/rhs-enabled", 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
30 changes: 20 additions & 10 deletions webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {GlobalState} from 'mattermost-redux/types/store';
import {RhsTitle} from 'components';

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

import {Rhs} from 'containers';
Expand All @@ -28,11 +28,10 @@ import 'styles/main.scss';
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.rhsEnabled.apiServiceName);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.needsConnect.apiServiceName);
makeApiRequestWithCompletionStatus(pluginApiServiceConfigs.getLinkedChannels.apiServiceName, linkedChannelsParams);
}, []);
Expand All @@ -43,7 +42,7 @@ const App = ({registry, store}:{registry: PluginRegistry, store: Store<GlobalSta
dispatch(setIsRhsLoading(isLoading));
}, [isLoading]);

const {data: whitelistUserData} = getApiState(pluginApiServiceConfigs.whitelistUser.apiServiceName);
const {data: rhsEnabledData} = getApiState(pluginApiServiceConfigs.rhsEnabled.apiServiceName);

useApiRequestCompletionState({
serviceName: pluginApiServiceConfigs.needsConnect.apiServiceName,
Expand All @@ -54,14 +53,21 @@ const App = ({registry, store}:{registry: PluginRegistry, store: Store<GlobalSta
});

useApiRequestCompletionState({
serviceName: pluginApiServiceConfigs.whitelistUser.apiServiceName,
serviceName: pluginApiServiceConfigs.rhsEnabled.apiServiceName,
handleSuccess: () => {
const {presentInWhitelist} = whitelistUserData as WhitelistUserResponse;
const {rhsEnabled} = rhsEnabledData as RhsEnabledResponse;
const rhsButtonData = localStorage.getItem(rhsButtonId);
if (rhsButtonData) {
const data = JSON.parse(rhsButtonData);
registry.unregisterComponent(data.headerId);
registry.unregisterComponent(data.appBarId);
localStorage.removeItem(rhsButtonId);
}

// Register the channel header button and app bar if the user is a whitelist user
if (presentInWhitelist) {
if (rhsEnabled) {
let appBarId;
const {_, toggleRHSPlugin} = registry.registerRightHandSidebarComponent(Rhs, <RhsTitle/>);
registry.registerChannelHeaderButtonAction(
const headerId = registry.registerChannelHeaderButtonAction(
<img
width={24}
height={24}
Expand All @@ -70,8 +76,12 @@ const App = ({registry, store}:{registry: PluginRegistry, store: Store<GlobalSta
/>, () => store.dispatch(toggleRHSPlugin), null, pluginTitle);

if (registry.registerAppBarComponent) {
registry.registerAppBarComponent(iconUrl, () => store.dispatch(toggleRHSPlugin), pluginTitle);
appBarId = registry.registerAppBarComponent(iconUrl, () => store.dispatch(toggleRHSPlugin), pluginTitle);
}
localStorage.setItem(rhsButtonId, JSON.stringify({
headerId,
appBarId,
}));
}
},
});
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
18 changes: 10 additions & 8 deletions webapp/src/components/RhsTitle/RhsTitle.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {iconUrl} from 'constants/illustrations.constants';
import {pluginTitle} from 'constants/common.constants';

export const RhsTitle = () => (
<span className='d-flex gap-8 align-items-center'>
<img
width={24}
height={24}
src={iconUrl}
/>
{pluginTitle}
</span>
<div className='msteams-sync-utils'>
<span className='d-flex gap-8 align-items-center'>
<img
width={24}
height={24}
src={iconUrl}
/>
{pluginTitle}
</span>
</div>
);
40 changes: 21 additions & 19 deletions webapp/src/components/Snackbar/Snackbar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,28 @@ export const Snackbar = () => {
};

return (
<div
className={`fixed bottom-20 right-20 left-20 py-8 px-12 rounded-4 d-flex gap-8 align-items-center justify-between elevation-2 msteams-sync-rhs__snackbar ${snackbarColorMap[severity]}`}
>
<div className='d-flex align-items-center gap-8'>
<Icon
iconName={snackbarIconMap[severity]}
className='icon-white icon-16'
/>
<h5 className='my-0 lh-24 wt-600 text-white'>{message}</h5>
</div>
<Button
variant='text'
className='snackbar__close'
onClick={handleClose}
<div className='msteams-sync-utils'>
<div
className={`fixed bottom-20 right-20 left-20 py-8 px-12 rounded-4 d-flex gap-8 align-items-center justify-between elevation-2 msteams-sync-rhs__snackbar ${snackbarColorMap[severity]}`}
>
<Icon
iconName='close'
className='icon-white icon-16'
/>
</Button>
<div className='d-flex align-items-center gap-8'>
<Icon
iconName={snackbarIconMap[severity]}
className='icon-white icon-16'
/>
<h5 className='my-0 lh-24 wt-600 text-white'>{message}</h5>
</div>
<Button
variant='text'
className='snackbar__close'
onClick={handleClose}
>
<Icon
iconName='close'
className='icon-white icon-16'
/>
</Button>
</div>
</div>
);
};
6 changes: 3 additions & 3 deletions webapp/src/constants/apiService.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export const pluginApiServiceConfigs: Record<PluginApiServiceName, PluginApiServ
method: 'GET',
apiServiceName: 'needsConnect',
},
whitelistUser: {
path: '/whitelist-user',
rhsEnabled: {
path: '/rhs-enabled',
method: 'GET',
apiServiceName: 'whitelistUser',
apiServiceName: 'rhsEnabled',
},
getLinkedChannels: {
path: '/linked-channels',
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/constants/common.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const defaultPage = 0;

export const defaultPerPage = 20;

export const rhsButtonId = 'rhsButtonId';

// Severity used in alert component
export const alertSeverity: Record<SnackbarColor, SnackbarColor> = {
success: 'success',
Expand Down
14 changes: 5 additions & 9 deletions webapp/src/containers/Rhs/Rhs.container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ export const Rhs = () => {

const {isOpen} = getSnackbarState(state);

const {data} = getApiState(pluginApiServiceConfigs.whitelistUser.apiServiceName);
const {data: linkedChannels} = getApiState(pluginApiServiceConfigs.getLinkedChannels.apiServiceName);

const {presentInWhitelist} = data as WhitelistUserResponse;

// NOTE: Commented out on purpose.This is part of Phase-II
// const isAnyChannelLinked = useMemo(() => Boolean((linkedChannels as ChannelLinkData[])?.length), [linkedChannels]);
const isAnyChannelLinked = false;

const getRhsView = useCallback(() => {
if (isRhsLoading) {
return (
<div className='absolute d-flex align-items-center justify-center w-full h-full'>
<Spinner size='xl'/>
<div className='msteams-sync-utils'>
<div className='absolute d-flex align-items-center justify-center w-full h-full'>
<Spinner size='xl'/>
</div>
</div>
);
}
Expand All @@ -57,10 +56,7 @@ export const Rhs = () => {

return (
<>
{
presentInWhitelist ?
getRhsView() : 'MS Teams Sync plugin'
}
{getRhsView()}
{isOpen && <Snackbar/>}
</>
);
Expand Down
Loading

0 comments on commit e3c7514

Please sign in to comment.