Skip to content

Commit

Permalink
Merge pull request #175 from blindsidenetworks/recording-choices
Browse files Browse the repository at this point in the history
Added choices to start meeting options to set recording preferences
  • Loading branch information
harshilsharma63 authored Sep 11, 2021
2 parents e2fe0be + 35b4775 commit 1f35020
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 76 deletions.
Binary file added assets/no_recording.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/recording.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 22 additions & 1 deletion server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package main

import (
"encoding/json"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/pkg/errors"
"strings"
)
Expand All @@ -30,6 +32,13 @@ type Configuration struct {
AllowExternalUsers bool `json:"ALLOW_EXTERNAL_USERS"`
}

func (c Configuration) AsMap() map[string]interface{} {
data, _ := json.Marshal(c)
var asMap *map[string]interface{}
_ = json.Unmarshal(data, &asMap)
return *asMap
}

func (p *Plugin) OnConfigurationChange() error {
var newConfig Configuration
// loads configuration from our config ui page
Expand All @@ -45,11 +54,23 @@ func (p *Plugin) OnConfigurationChange() error {
p.job.Close()
}

// stores the config in an Atomic.Value place
if p.configuration.Load() != nil {
p.broadcastConfigChange(newConfig)
}

// stores the config in an `Atomic.Value` place
p.configuration.Store(&newConfig)
return err
}

func (p *Plugin) broadcastConfigChange(config Configuration) {
payload := map[string]interface{}{
"config": config.AsMap(),
}

p.API.PublishWebSocketEvent("config_update", payload, &model.WebsocketBroadcast{})
}

func (p *Plugin) config() *Configuration {
// returns the config file we had stored in Atomic.Value
config := p.configuration.Load()
Expand Down
19 changes: 15 additions & 4 deletions server/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const (
prefixMeetingList = "m_list_"
)

func (p *Plugin) PopulateMeeting(m *dataStructs.MeetingRoom, details []string, description string, UserId string, channelId string) error {
func (p *Plugin) PopulateMeeting(m *dataStructs.MeetingRoom, details []string, description string, UserId string, channelId string, allowRecording bool) error {
if len(details) == 2 {
m.Name_ = details[1]
} else {
Expand All @@ -61,8 +61,8 @@ func (p *Plugin) PopulateMeeting(m *dataStructs.MeetingRoom, details []string, d
m.MeetingID_ = GenerateRandomID()
m.AttendeePW_ = "ap"
m.ModeratorPW_ = "mp"
m.Record = strconv.FormatBool(p.config().AllowRecordings)
m.AllowStartStopRecording = p.config().AllowRecordings
m.Record = strconv.FormatBool(p.config().AllowRecordings && allowRecording)
m.AllowStartStopRecording = p.config().AllowRecordings && allowRecording
m.AutoStartRecording = false
m.Meta = description
var RedirectUrl *url.URL
Expand Down Expand Up @@ -138,13 +138,18 @@ func (p *Plugin) createStartMeetingPost(userId string, channelId string, m *data

attachments := []*model.SlackAttachment{
{
Text: titlePrefix + "Meeting created by @" + user.Username,
Text: titlePrefix + "Meeting created by @" + user.Username + "\n\n",
Fields: []*model.SlackAttachmentField{
{
Title: "Attendees",
Value: "*There are no attendees in this session*",
Short: false,
},
{
Title: "Recording",
Value: "",
Short: false,
},
{
Title: "",
Value: "",
Expand Down Expand Up @@ -180,6 +185,12 @@ func (p *Plugin) createStartMeetingPost(userId string, channelId string, m *data
},
}

if m.AllowStartStopRecording {
attachments[0].Fields[1].Value = "Allowed"
} else {
attachments[0].Fields[1].Value = "Disabled"
}

if p.config().AllowExternalUsers {
attachments[0].Fields = append(attachments[0].Fields,
&model.SlackAttachmentField{
Expand Down
17 changes: 16 additions & 1 deletion server/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ func (p *Plugin) OnActivate() error {
Trigger: "bbb",
AutoComplete: true,
AutoCompleteDesc: "Create a BigBlueButton meeting",
AutocompleteData: &model.AutocompleteData{
Trigger: "bbb",
HelpText: "Start a new BigBlueButton recording",
RoleID: model.SYSTEM_USER_ROLE_ID,
SubCommands: []*model.AutocompleteData{
{
Trigger: "no_recording",
HelpText: "Start a new meeting with recording disabled",
RoleID: model.SYSTEM_USER_ROLE_ID,
},
},
},
})
}

Expand Down Expand Up @@ -122,7 +134,8 @@ func (p *Plugin) schedule() error {
func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
meetingpointer := new(dataStructs.MeetingRoom)

if err := p.PopulateMeeting(meetingpointer, nil, "", args.UserId, args.ChannelId); err != nil {
allowRecording := !strings.Contains(args.Command, "no_recording")
if err := p.PopulateMeeting(meetingpointer, nil, "", args.UserId, args.ChannelId, allowRecording); err != nil {
return nil, model.NewAppError("ExecuteCommand", "Please provide a 'Site URL' in Settings > General > Configuration", nil, err.Error(), http.StatusInternalServerError)
}

Expand Down Expand Up @@ -168,6 +181,8 @@ func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Req
p.handleImmediateEndMeetingCallback(w, r, path)
} else if path == "/ismeetingrunning" {
p.handleIsMeetingRunning(w, r)
} else if path == "/config" {
p.handleGetConfig(w, r)
} else if path == "/redirect" {
// html file to automatically close a window
// nolint:staticcheck
Expand Down
31 changes: 25 additions & 6 deletions server/responsehandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ import (
)

type RequestCreateMeetingJSON struct {
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
Topic string `json:"title"`
Desc string `json:"description"`
UserId string `json:"user_id"`
ChannelId string `json:"channel_id"`
Topic string `json:"title"`
Desc string `json:"description"`
AllowRecording bool `json:"allow_recording"`
}

type ButtonRequestJSON struct {
Expand Down Expand Up @@ -186,9 +187,9 @@ func (p *Plugin) handleCreateMeeting(w http.ResponseWriter, r *http.Request) {
meetingpointer := new(dataStructs.MeetingRoom)
var err error
if request.Topic == "" {
err = p.PopulateMeeting(meetingpointer, nil, request.Desc, request.UserId, request.ChannelId)
err = p.PopulateMeeting(meetingpointer, nil, request.Desc, request.UserId, request.ChannelId, request.AllowRecording)
} else {
err = p.PopulateMeeting(meetingpointer, []string{"create", request.Topic}, request.Desc, request.UserId, request.ChannelId)
err = p.PopulateMeeting(meetingpointer, []string{"create", request.Topic}, request.Desc, request.UserId, request.ChannelId, request.AllowRecording)
}

if err != nil {
Expand Down Expand Up @@ -952,6 +953,24 @@ func (p *Plugin) handleDeleteRecordings(w http.ResponseWriter, r *http.Request)
w.WriteHeader(http.StatusOK)
}

func (p *Plugin) handleGetConfig(w http.ResponseWriter, r *http.Request) {
config := p.config()

sanitizedConfig := &Configuration{
AllowRecordings: config.AllowRecordings,
}

data, err := json.Marshal(sanitizedConfig)
if err != nil {
p.API.LogError("Error occurred marshaling sanitizing config.", "error", err.Error())
http.Error(w, "Error occurred marshaling sanitizing config", http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(data)
}

type isRunningResponseJSON struct {
IsRunning bool `json:"running"`
}
2 changes: 1 addition & 1 deletion webapp/action_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import PluginId from './plugin_id';

// Namespace your actions to avoid collisions.
export const STATUS_CHANGE = PluginId + '_status_change';

export const OPEN_ROOT_MODAL = PluginId + '_open_root_modal';
export const CLOSE_ROOT_MODAL = PluginId + '_close_root_modal';
export const SET_PLUGIN_CONFIG = PluginId + '_set_plugin_config';
4 changes: 2 additions & 2 deletions webapp/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export const closeRootModal = () => (dispatch) => {
export const mainMenuAction = openRootModal;
export const channelHeaderButtonAction = openRootModal;

export function startMeeting(channelId, description = '', topic = '', meetingId = 0) {
export function startMeeting(channelId, allowRecording, description = '', topic = '', meetingId = 0) {
return async (dispatch, getState) => {
try {
await GetClient().startMeeting(getState().entities.users.currentUserId, channelId, topic, description);
await GetClient().startMeeting(getState().entities.users.currentUserId, channelId, topic, description, allowRecording);
} catch (error) {
var message_text = 'BigBlueButton did not successfully start a meeting';
if (error.status == 422 ) { // SiteURL is not set
Expand Down
9 changes: 7 additions & 2 deletions webapp/client/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ export default class Client {
return `${this.siteUrlFunc()}/plugins/bigbluebutton`
}

startMeeting = async (userid, channelid, topic, description) => {
startMeeting = async (userid, channelid, topic, description, allowRecording) => {
return this.doPost(`${this.baseUrl}/create`, {
user_id: userid,
channel_id: channelid,
title: topic,
description: description
description: description,
allow_recording: allowRecording,
});
}

Expand Down Expand Up @@ -77,6 +78,10 @@ export default class Client {
});
}

getPluginConfig = async () => {
return await this.doPost(`${this.baseUrl}/config`)
}

doPost = async (url, body, headers = {}) => {
const options = Client4.getOptions({
method: 'post',
Expand Down
4 changes: 3 additions & 1 deletion webapp/components/root/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
import {getLastPostPerChannel} from 'mattermost-redux/selectors/entities/posts';
import {getChannelsInCurrentTeam, getDirectChannels, getSortedUnreadChannelIds, makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getTheme} from 'mattermost-redux/selectors/entities/preferences';
import {isRootModalVisible} from '../../selectors';
import {getPluginState, isRootModalVisible} from '../../selectors';
import {getSortedDirectChannelWithUnreadsIds} from 'mattermost-redux/selectors/entities/channels';
import {getJoinURL,startMeeting, showRecordings,closeRootModal} from '../../actions';

Expand All @@ -45,6 +45,7 @@ function mapStateToProps(state, ownProps) {
channelId = '';
}
let teamId = state.entities.teams.currentTeamId;
const pluginConfig = getPluginState(state).pluginConfig

return {
visible: isRootModalVisible(state),
Expand All @@ -59,6 +60,7 @@ function mapStateToProps(state, ownProps) {
theme: getTheme(state),
lastpostperchannel: getLastPostPerChannel(state),
unreadChannelIds: getSortedDirectChannelWithUnreadsIds(state, keepChannelIdAsUnread),
pluginConfig,
...ownProps
};
}
Expand Down
36 changes: 16 additions & 20 deletions webapp/components/root/popover_list_members_item.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class PopoverListMembersItem extends React.PureComponent {
static propTypes = {
onItemClick: PropTypes.func.isRequired,
text: PropTypes.element.isRequired,
cam: PropTypes.number.isRequired,
icon: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
ariaLabel: PropTypes.string,
};
Expand All @@ -52,19 +52,24 @@ export default class PopoverListMembersItem extends React.PureComponent {
render() {
const style = getStyle(this.props.theme);

console.log(this.props);
console.log(this.props.icon);
console.log(Svgs[this.props.icon]);

return (
<button
aria-label={this.props.ariaLabel}
className={'style--none'}
onMouseEnter={this.rowStartShowHover}
onMouseLeave={this.rowStartHideHover}
onClick={this.handleClick}
style={this.state.rowStartHover ? style.popoverRowHover : style.popoverRowNoHover}
style={this.state.rowStartHover ? {...style.popoverRowBase, ...style.popoverRowHover} : style.popoverRowBase}
>
<span
style={style.popoverIcon}
className='pull-left'
dangerouslySetInnerHTML={this.props.cam == 1 ? {__html: Svgs.BBBCAM} : {__html: Svgs.VID_CAM_PLAY}}
// dangerouslySetInnerHTML={this.props.cam == 1 ? {__html: Svgs.BBBCAM} : {__html: Svgs.VID_CAM_PLAY}}
dangerouslySetInnerHTML={{__html: Svgs[this.props.icon]}}
aria-hidden='true'
/>
<div style={style.popoverRow}>
Expand All @@ -80,18 +85,16 @@ const getStyle = makeStyleFromTheme((theme) => {
return {

popoverRow: {
border: 'none',
cursor: 'pointer',
height: '50px',
margin: '1px 0',
overflow: 'auto',
padding: '6px 19px 0 10px',
padding: '0 12px',
},
popoverRowNoHover: {
popoverRowBase: {
borderLeft: '3px solid',
borderColor: theme.centerChannelBg,
fontWeight: 'normal',
width: '100%',
display: 'flex',
height: '60px',
padding: '6px 12px',
},
popoverRowHover: {
borderLeft: '3px solid transparent',
Expand All @@ -101,19 +104,12 @@ const getStyle = makeStyleFromTheme((theme) => {
},
popoverText: {
fontWeight: 'inherit',
fontSize: '14px',
position: 'relative',
top: '10px',
left: '4px',
fontSize: '13px',
textAlign: 'left',
},
popoverIcon: {
margin: '0',
paddingLeft: '16px',
position: 'relative',
top: '12px',
fontSize: '20px',
fill: theme.buttonBg,
height: '90%',
padding: '4px',
},
};
});
Loading

0 comments on commit 1f35020

Please sign in to comment.