Skip to content

Commit

Permalink
Add new plugin setting to toggle button visibility in team sidebar (#114
Browse files Browse the repository at this point in the history
)

* Add new action type to toggle team sidebar

* Add new setting into plugin for system console

* Add new reducer to manage team sidebar visibilty

* Add new key to server configuration

- Fix plugin key name

* Add new action to toggle team sidebar visibility

* Add new APIs for config update

- Add API to retrieve config
- Add WebSocket event to frontend to update config

* Send WebSocket event on config update

* Add visible flag to team sidebar component

- Map visibility state to component prop

* Add config update task to plugin base

- Retrieve config on initialization
- Register WebSocket event for config update

* Update system console setting description

* Fix lint errors

* Rename key for plugin setting

* Change reducer and action name

* Change config endpoint to return client config

* Change WebSocket event to directly update config

* Remove unneeded variable

Co-authored-by: Michael Kochell <[email protected]>

* Dispatch WebSocket event if there are changes

* Rename selector for clarity

* Rename action type for clarity

* Fix lint errors

* Rename variable

* Add null check for config to fix first setup

Co-authored-by: Michael Kochell <[email protected]>
  • Loading branch information
colorfusion and mickmister authored Sep 2, 2020
1 parent ccfcd25 commit c865928
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 7 deletions.
7 changes: 6 additions & 1 deletion plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"settings_schema": {
"header": "",
"footer": "",
"settings": []
"settings": [{
"key": "hide_team_sidebar",
"display_name": "Hide team sidebar buttons",
"type": "bool",
"help_text": "When true, the buttons in the team sidebar on the left toolbar will be hidden."
}]
}
}
12 changes: 12 additions & 0 deletions server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
// If you add non-reference types to your configuration struct, be sure to rewrite Clone as a deep
// copy appropriate for your types.
type configuration struct {
HideTeamSidebar bool `json:"hide_team_sidebar"`
}

// Clone shallow copies the configuration. Your implementation may require a deep copy if
Expand Down Expand Up @@ -72,6 +73,11 @@ func (p *Plugin) setConfiguration(configuration *configuration) {
p.configuration = configuration
}

// Check whether client configuration are different
func (p *Plugin) hasClientConfigChanged(prev *configuration, current *configuration) bool {
return prev == nil || prev.HideTeamSidebar != current.HideTeamSidebar
}

// OnConfigurationChange is invoked when configuration changes may have been made.
func (p *Plugin) OnConfigurationChange() error {
var configuration = new(configuration)
Expand All @@ -81,7 +87,13 @@ func (p *Plugin) OnConfigurationChange() error {
return errors.Wrap(err, "failed to load plugin configuration")
}

shouldUpdateClient := p.hasClientConfigChanged(p.configuration, configuration)
p.setConfiguration(configuration)

// Dispatch WebSocket event to send all users updated client configs
if shouldUpdateClient {
p.sendConfigUpdateEvent()
}

return nil
}
53 changes: 53 additions & 0 deletions server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import (
const (
// WSEventRefresh is the WebSocket event for refreshing the Todo list
WSEventRefresh = "refresh"

// WSEventConfigUpdate is the WebSocket event to update the Todo list's configurations on webapp
WSEventConfigUpdate = "config_update"
)

// ListManager represents the logic on the lists
Expand Down Expand Up @@ -92,6 +95,8 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
p.handleAccept(w, r)
case "/bump":
p.handleBump(w, r)
case "/config":
p.handleConfig(w, r)
default:
http.NotFound(w, r)
}
Expand Down Expand Up @@ -395,6 +400,41 @@ func (p *Plugin) handleBump(w http.ResponseWriter, r *http.Request) {
p.PostBotCustomDM(foreignUser, message, todoMessage, foreignIssueID)
}

// API endpoint to retrieve plugin configurations
func (p *Plugin) handleConfig(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("Mattermost-User-ID")
if userID == "" {
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}

if r.Method != http.MethodGet {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}

if p.configuration != nil {
// retrieve client only configurations
clientConfig := struct {
HideTeamSidebar bool `json:"hide_team_sidebar"`
}{
HideTeamSidebar: p.configuration.HideTeamSidebar,
}

configJSON, err := json.Marshal(clientConfig)
if err != nil {
p.API.LogError("Unable to marshal plugin configuration to json err=" + err.Error())
p.handleErrorWithCode(w, http.StatusInternalServerError, "Unable to marshal plugin configuration to json", err)
return
}

_, err = w.Write(configJSON)
if err != nil {
p.API.LogError("Unable to write json response err=" + err.Error())
}
}
}

func (p *Plugin) sendRefreshEvent(userID string) {
p.API.PublishWebSocketEvent(
WSEventRefresh,
Expand All @@ -403,6 +443,19 @@ func (p *Plugin) sendRefreshEvent(userID string) {
)
}

// Publish a WebSocket event to update the client config of the plugin on the webapp end.
func (p *Plugin) sendConfigUpdateEvent() {
clientConfigMap := map[string]interface{}{
"hide_team_sidebar": p.configuration.HideTeamSidebar,
}

p.API.PublishWebSocketEvent(
WSEventConfigUpdate,
clientConfigMap,
&model.WebsocketBroadcast{},
)
}

func (p *Plugin) handleErrorWithCode(w http.ResponseWriter, code int, errTitle string, err error) {
w.WriteHeader(code)
b, _ := json.Marshal(struct {
Expand Down
1 change: 1 addition & 0 deletions webapp/src/action_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export const GET_IN_ISSUES = pluginId + '_get_in_issues';
export const RECEIVED_SHOW_RHS_ACTION = pluginId + '_show_rhs';
export const UPDATE_RHS_STATE = pluginId + '_update_rhs_state';
export const SET_RHS_VISIBLE = pluginId + '_set_rhs_visible';
export const SET_HIDE_TEAM_SIDEBAR_BUTTONS = pluginId + '_set_hide_team_sidebar';
36 changes: 35 additions & 1 deletion webapp/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ import {Client4} from 'mattermost-redux/client';
import * as UserActions from 'mattermost-redux/actions/users';

import {id as pluginId} from './manifest';
import {OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL, RECEIVED_SHOW_RHS_ACTION, GET_ISSUES, GET_IN_ISSUES, GET_OUT_ISSUES, UPDATE_RHS_STATE, SET_RHS_VISIBLE} from './action_types';
import {
OPEN_ROOT_MODAL,
CLOSE_ROOT_MODAL,
RECEIVED_SHOW_RHS_ACTION,
GET_ISSUES,
GET_IN_ISSUES,
GET_OUT_ISSUES,
UPDATE_RHS_STATE,
SET_RHS_VISIBLE,
SET_HIDE_TEAM_SIDEBAR_BUTTONS,
} from './action_types';

export const openRootModal = (postID) => (dispatch) => {
dispatch({
Expand Down Expand Up @@ -150,3 +160,27 @@ export function autocompleteUsers(username) {
return data;
};
}

export function setHideTeamSidebar(payload) {
return {
type: SET_HIDE_TEAM_SIDEBAR_BUTTONS,
payload,
};
}

export const updateConfig = () => async (dispatch, getState) => {
let resp;
let data;
try {
resp = await fetch(getPluginServerRoute(getState()) + '/config', Client4.getOptions({
method: 'get',
}));
data = await resp.json();
} catch (error) {
return {error};
}

dispatch(setHideTeamSidebar(data.hide_team_sidebar));

return {data};
};
5 changes: 4 additions & 1 deletion webapp/src/components/team_sidebar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@

import {connect} from 'react-redux';

import {isTeamSidebarVisible} from 'selectors';

import TeamSidebar from './team_sidebar.jsx';

function mapStateToProps(state) {
const members = state.entities.teams.myMembers || {};
return {
show: Object.keys(members).length > 1,
visible: isTeamSidebarVisible(state),
};
}

export default connect(mapStateToProps)(TeamSidebar);
export default connect(mapStateToProps)(TeamSidebar);
5 changes: 3 additions & 2 deletions webapp/src/components/team_sidebar/team_sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ export default class TeamSidebar extends React.PureComponent {
static propTypes = {
show: PropTypes.bool.isRequired,
theme: PropTypes.object.isRequired,
visible: PropTypes.bool.isRequired,
};

render() {
if (!this.props.show) {
if (!this.props.show || !this.props.visible) {
return null;
}

Expand All @@ -24,4 +25,4 @@ export default class TeamSidebar extends React.PureComponent {
/>
);
}
}
}
11 changes: 10 additions & 1 deletion webapp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {id as pluginId} from './manifest';
import Root from './components/root';
import SidebarRight from './components/sidebar_right';

import {openRootModal, list, setShowRHSAction} from './actions';
import {openRootModal, list, setShowRHSAction, updateConfig, setHideTeamSidebar} from './actions';
import reducer from './reducer';
import PostTypeTodo from './components/post_type_todo';
import TeamSidebar from './components/team_sidebar';
Expand Down Expand Up @@ -44,6 +44,15 @@ export default class Plugin {
store.dispatch(list(false, 'in'));
store.dispatch(list(false, 'out'));

// register websocket event to track config changes
const configUpdate = ({data}) => {
store.dispatch(setHideTeamSidebar(data.hide_team_sidebar));
};

registry.registerWebSocketEventHandler(`custom_${pluginId}_config_update`, configUpdate);

store.dispatch(updateConfig());

activityFunc = () => {
const now = new Date().getTime();
if (now - lastActivityTime > activityTimeout) {
Expand Down
12 changes: 11 additions & 1 deletion webapp/src/reducer.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {combineReducers} from 'redux';

import {OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL, GET_ISSUES, GET_IN_ISSUES, GET_OUT_ISSUES, RECEIVED_SHOW_RHS_ACTION, UPDATE_RHS_STATE, SET_RHS_VISIBLE} from './action_types';
import {OPEN_ROOT_MODAL, CLOSE_ROOT_MODAL, GET_ISSUES, GET_IN_ISSUES, GET_OUT_ISSUES, RECEIVED_SHOW_RHS_ACTION, UPDATE_RHS_STATE, SET_RHS_VISIBLE, SET_HIDE_TEAM_SIDEBAR_BUTTONS} from './action_types';

const rootModalVisible = (state = false, action) => {
switch (action.type) {
Expand Down Expand Up @@ -78,6 +78,15 @@ function isRhsVisible(state = false, action) {
}
}

function isTeamSidebarHidden(state = false, action) {
switch (action.type) {
case SET_HIDE_TEAM_SIDEBAR_BUTTONS:
return action.payload;
default:
return state;
}
}

export default combineReducers({
rootModalVisible,
postID,
Expand All @@ -87,4 +96,5 @@ export default combineReducers({
rhsState,
rhsPluginAction,
isRhsVisible,
isTeamSidebarHidden,
});
1 change: 1 addition & 0 deletions webapp/src/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ export const getSiteURL = () => {
};

export const isRhsVisible = (state) => getPluginState(state).isRhsVisible;
export const isTeamSidebarVisible = (state) => !getPluginState(state).isTeamSidebarHidden;

0 comments on commit c865928

Please sign in to comment.