From 12dbc7e04e5e2e17353bee009275d46fbd333ddc Mon Sep 17 00:00:00 2001 From: Jason Gillett Date: Mon, 26 Sep 2022 11:41:51 -0600 Subject: [PATCH] Allow selecting sections and groups in BBB modal This commit should allow all sections and groups to be selected closes: VICE-3096 flag=bbb_modal_update Note Groups and sections prepopulate the address book when: - All of their students have been invited - If they have no users This matches legacy behavior The behavior of the section/group/user selection is not intuitive This commit should match legacy behavior. Please check legacy if you see behavior you think is broken Test Plan: 1. Create course with groups and Sections 2. Create new Conference 3. Verify that groups and sections appear 4. invite a group or section 5. Create the conference 6. Edit the newly created conference and open the addressbook 7. Verify that all users in group or section were invited 8. Verify group/section tag populates the addressbook 8a. Verify that the groups and sections selected match legacy Change-Id: I1881eee5f45b4261c0b8ccf344330a017cc5cf19 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/301675 Tested-by: Service Cloud Jenkins QA-Review: Caleb Guanzon Reviewed-by: Jeffrey Johnson Product-Review: Jeffrey Johnson --- .../big_blue_button_conference_spec.rb | 46 ++++ ui/features/conferences/index.js | 71 +++++- .../BBBModalOptions/BBBModalOptions.js | 14 +- .../BaseModalOptions/BaseModalOptions.js | 8 +- .../ConferenceAddressBook.js | 209 +++++++++++++++--- .../__tests__/ConferenceAddressBook.test.js | 109 ++++++++- .../VideoConferenceModal.js | 5 +- 7 files changed, 393 insertions(+), 69 deletions(-) diff --git a/spec/selenium/conferences/big_blue_button_conference_spec.rb b/spec/selenium/conferences/big_blue_button_conference_spec.rb index e84ac53fa85..e84bca39dac 100644 --- a/spec/selenium/conferences/big_blue_button_conference_spec.rb +++ b/spec/selenium/conferences/big_blue_button_conference_spec.rb @@ -82,6 +82,52 @@ describe "BigBlueButton conferences" do end end + context "attendee selection" do + before do + @section = @course.course_sections.create!(name: "test section") + student_in_section(@section, user: @student) + + @group_category = @course.group_categories.create!(name: "Group Category") + @group = @course.groups.create!(group_category: @group_category, name: "Group 1") + @group.add_user(@student, "accepted") + end + + context "on create" do + it "successfully invites a section to the conference" do + get "/courses/#{@course.id}/conferences" + new_conference_button.click + wait_for_ajaximations + f("div#tab-attendees").click + fj("label:contains('Invite all course members')").click + f("[data-testid='address-input']").click + f("[data-testid='section-#{@section.id}']").click + expect(@section.participants.count).to eq ff("[data-testid='address-tag']").count + + wait_for_new_page_load { f("button[data-testid='submit-button']").click } + new_conference = WebConference.last + expect(@section.participants.count).to eq new_conference.users.count + end + + it "successfully invites a group to the conference" do + get "/courses/#{@course.id}/conferences" + # Since the teacher isn't a participating user, we have to add 1 to this count + group_participant_and_group_tag_count = @group.participating_users_in_context.count + 1 + + new_conference_button.click + wait_for_ajaximations + f("div#tab-attendees").click + fj("label:contains('Invite all course members')").click + f("[data-testid='address-input']").click + f("[data-testid='group-#{@group.id}']").click + expect(group_participant_and_group_tag_count).to eq ff("[data-testid='address-tag']").count + + wait_for_new_page_load { f("button[data-testid='submit-button']").click } + new_conference = WebConference.last + expect(group_participant_and_group_tag_count).to eq new_conference.users.count + end + end + end + it "validates name length" do initial_conference_count = WebConference.count get conferences_index_page diff --git a/ui/features/conferences/index.js b/ui/features/conferences/index.js index 1d5006a8ab2..62fd988cf85 100644 --- a/ui/features/conferences/index.js +++ b/ui/features/conferences/index.js @@ -117,14 +117,35 @@ const ConferencesRouter = Backbone.Router.extend({ return { displayName: name, id, + type: 'user', + assetCode: `user-${id}`, } }) + const availableSectionsList = ENV.sections.map(({id, name}) => { + return { + displayName: name, + id, + type: 'section', + assetCode: `section-${id}`, + } + }) + + const availableGroupsList = ENV.groups.map(({id, name}) => { + return { + displayName: name, + id, + type: 'group', + assetCode: `group-${id}`, + } + }) + + const menuData = availableAttendeesList.concat(availableSectionsList, availableGroupsList) ReactDOM.render( { window.location.hash = '' ReactDOM.render(, document.getElementById('react-conference-modal-container')) @@ -174,8 +195,14 @@ const ConferencesRouter = Backbone.Router.extend({ payload[`user[${userId}]`] = 1 }) } else { - data.selectedAttendees.forEach(userId => { - payload[`user[${userId}]`] = 1 + data.selectedAttendees.forEach(menuItem => { + if (menuItem.type === 'group') { + payload[`group[${menuItem.id}]`] = 1 + } else if (menuItem.type === 'section') { + payload[`section[${menuItem.id}]`] = 1 + } else { + payload[`user[${menuItem.id}]`] = 1 + } }) } @@ -242,9 +269,30 @@ const ConferencesRouter = Backbone.Router.extend({ return { displayName: name, id, + type: 'user', + assetCode: `user-${id}`, } }) + const availableSectionsList = ENV.sections.map(({id, name}) => { + return { + displayName: name, + id, + type: 'section', + assetCode: `section-${id}`, + } + }) + + const availableGroupsList = ENV.groups.map(({id, name}) => { + return { + displayName: name, + id, + type: 'group', + assetCode: `group-${id}`, + } + }) + + const menuData = availableAttendeesList.concat(availableSectionsList, availableGroupsList) if (attributes.long_running === 1) { options.push('no_time_limit') } @@ -285,9 +333,10 @@ const ConferencesRouter = Backbone.Router.extend({ description={attributes.description} invitationOptions={invitationOptions} attendeesOptions={attendeesOptions} - availableAttendeesList={availableAttendeesList} - selectedAttendees={attributes.user_ids} - savedAttendees={attributes.user_ids} + availableAttendeesList={menuData} + selectedAttendees={attributes.user_ids.map(u => { + return {assetCode: `user-${u}`, id: u} + })} startCalendarDate={attributes.start_at} endCalendarDate={attributes.end_at} onDismiss={() => { @@ -340,8 +389,14 @@ const ConferencesRouter = Backbone.Router.extend({ payload[`user[${userId}]`] = 1 }) } else { - data.selectedAttendees.forEach(userId => { - payload[`user[${userId}]`] = 1 + data.selectedAttendees.forEach(menuItem => { + if (menuItem.type === 'group') { + payload[`group[${menuItem.id}]`] = 1 + } else if (menuItem.type === 'section') { + payload[`section[${menuItem.id}]`] = 1 + } else { + payload[`user[${menuItem.id}]`] = 1 + } }) } diff --git a/ui/features/conferences/react/components/BBBModalOptions/BBBModalOptions.js b/ui/features/conferences/react/components/BBBModalOptions/BBBModalOptions.js index f852932d11d..7373aaad350 100644 --- a/ui/features/conferences/react/components/BBBModalOptions/BBBModalOptions.js +++ b/ui/features/conferences/react/components/BBBModalOptions/BBBModalOptions.js @@ -41,7 +41,7 @@ const BBBModalOptions = ({addToCalendar, setAddToCalendar, ...props}) => { const [noTimeLimit, setNoTimeLimit] = useState(props.options.includes('no_time_limit')) // match options.no_time_limit default const contextIsGroup = ENV.context_asset_string?.split('_')[0] === 'group' - const inviteAllMemberstext = contextIsGroup + const inviteAllMembersText = contextIsGroup ? I18n.t('Invite all group members') : I18n.t('Invite all course members') @@ -249,7 +249,7 @@ const BBBModalOptions = ({addToCalendar, setAddToCalendar, ...props}) => { } > - + {!contextIsGroup && ( { { - props.onAttendeesChange(userList.map(u => u.id)) + onChange={menuItemList => { + props.onAttendeesChange(menuItemList) }} isEditing={props.isEditing} /> @@ -335,8 +334,7 @@ BBBModalOptions.propTypes = { showAddressBook: PropTypes.bool, onAttendeesChange: PropTypes.func, availableAttendeesList: PropTypes.arrayOf(PropTypes.object), - selectedAttendees: PropTypes.arrayOf(PropTypes.string), - savedAttendees: PropTypes.arrayOf(PropTypes.string), + selectedAttendees: PropTypes.arrayOf(PropTypes.object), showCalendar: PropTypes.bool, setAddToCalendar: PropTypes.func, addToCalendar: PropTypes.bool, diff --git a/ui/features/conferences/react/components/BaseModalOptions/BaseModalOptions.js b/ui/features/conferences/react/components/BaseModalOptions/BaseModalOptions.js index 322ad844808..3b609bb506e 100644 --- a/ui/features/conferences/react/components/BaseModalOptions/BaseModalOptions.js +++ b/ui/features/conferences/react/components/BaseModalOptions/BaseModalOptions.js @@ -113,11 +113,10 @@ const BaseModalOptions = props => { { - props.onAttendeesChange(userList.map(u => u.id)) + onChange={menuItemList => { + props.onAttendeesChange(menuItemList) }} isEditing={props.isEditing} /> @@ -154,7 +153,6 @@ BaseModalOptions.propTypes = { onAttendeesChange: PropTypes.func, availableAttendeesList: PropTypes.arrayOf(PropTypes.object), selectedAttendees: PropTypes.arrayOf(PropTypes.string), - savedAttendees: PropTypes.arrayOf(PropTypes.string), nameValidationMessages: PropTypes.array, descriptionValidationMessages: PropTypes.array, hasBegun: PropTypes.bool, diff --git a/ui/features/conferences/react/components/ConferenceAddressBook/ConferenceAddressBook.js b/ui/features/conferences/react/components/ConferenceAddressBook/ConferenceAddressBook.js index de83cdf4b14..252e6697ba4 100644 --- a/ui/features/conferences/react/components/ConferenceAddressBook/ConferenceAddressBook.js +++ b/ui/features/conferences/react/components/ConferenceAddressBook/ConferenceAddressBook.js @@ -24,27 +24,68 @@ import {Tag} from '@instructure/ui-tag' const I18n = useI18nScope('video_conference') -export const ConferenceAddressBook = ({ - menuItemList, - onChange, - selectedIds, - isEditing, - savedAttendees, -}) => { +export const ConferenceAddressBook = ({menuItemList, onChange, selectedItems, isEditing}) => { const [isOpen, setIsOpen] = useState(false) const [highlightMenuItem, setHighlightMenuItem] = useState(null) const [inputValue, setInputValue] = useState('') const [selectedMenuItems, setSelectedMenuItems] = useState([]) const [announcement, setAnnouncement] = useState('') + const [savedAttendees, setSavedAttendees] = useState([]) - // Initial setup of selectd Ids - useEffect(() => { - const initialSelectedMenuItems = menuItemList.filter(u => selectedIds?.includes(u.id)) - if (initialSelectedMenuItems !== selectedMenuItems) { - setSelectedMenuItems(initialSelectedMenuItems) + const groupUserMap = ENV?.group_user_ids_map || {} + const sectionUserMap = ENV?.section_user_ids_map || {} + + // Create an array that contains the shared elements between 2 arrays + const intersection = (array1, array2) => { + let tempSwitchVariable + if (array2.length > array1.length) { + tempSwitchVariable = array2 + array2 = array1 + array1 = tempSwitchVariable } + return array1.filter(e => { + return array2.indexOf(e) > -1 + }) + } + // Runs once on startup to set up initially selected items + useEffect(() => { + const selectedUserIDs = selectedItems?.map(u => u.id) + const selectedUserAssetCode = selectedItems?.map(u => u.assetCode) + // This should only get set once. Represents users who are already a part of the conference + + const sectionIDs = ENV.sections?.map(u => u.id) || [] + const groupIDs = ENV.groups?.map(u => u.id) || [] + + let selectedSections = [] + let selectedGroups = [] + + // Any section or group that has all of its students selected will be auto selected + // Empty groups or sections will be set to selected automatically + sectionIDs?.forEach(id => { + const sectionUsers = sectionUserMap[id] + const intersectionArray = intersection(sectionUsers, selectedUserIDs) + if (intersectionArray.length === sectionUsers.length) { + selectedSections.push(id) + } + }) + + groupIDs?.forEach(id => { + const groupUsers = groupUserMap[id] + const intersectionArray = intersection(groupUsers, selectedUserIDs) + if (intersectionArray.length === groupUsers.length) { + selectedGroups.push(id) + } + }) + + selectedGroups = selectedGroups?.map(u => `group-${u}`) + selectedSections = selectedSections?.map(u => `section-${u}`) + const initialSelectedMenuItems = menuItemList.filter(u => + selectedGroups.concat(selectedSections, selectedUserAssetCode)?.includes(u.assetCode) + ) + setSavedAttendees(initialSelectedMenuItems.map(u => u.assetCode)) + setSelectedMenuItems([...selectedMenuItems.concat(initialSelectedMenuItems)]) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedIds, menuItemList]) + }, []) const handleBlur = () => { setHighlightMenuItem(null) @@ -59,7 +100,7 @@ export const ConferenceAddressBook = ({ const handleHighlight = (e, {id}) => { if (id) { - const menuItem = menuItemList.find(u => u.id === id) + const menuItem = menuItemList.find(u => u.assetCode === id) setHighlightMenuItem(menuItem) setAnnouncement(menuItem.displayName) } @@ -93,24 +134,62 @@ export const ConferenceAddressBook = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [inputValue, menuItemList, selectedMenuItems.length]) + const mapOfFilteredMenuItems = useMemo(() => { + const sectionArray = [] + const groupArray = [] + const userArray = [] + + filteredMenuItems.forEach(menuItem => { + if (menuItem.type === 'section') { + sectionArray.push(menuItem) + } else if (menuItem.type === 'group') { + groupArray.push(menuItem) + } else { + userArray.push(menuItem) + } + }) + + return new Map([ + ['sections', sectionArray], + ['groups', groupArray], + ['users', userArray], + ]) + }, [filteredMenuItems]) + const removeSelectedItem = menuItem => { if (isEditing) { - if (savedAttendees?.includes(menuItem.id)) + // Change this to work with asset codes + if (savedAttendees?.includes(menuItem.assetCode)) { // terminate if menu item has been saved return + } } + + // Get users from group or section to Remove + let additionalUsersToRemove = [] + if (menuItem.type === 'group') { + additionalUsersToRemove = groupUserMap[menuItem.id]?.map(u => `user-${u}`) + } else if (menuItem.type === 'section') { + additionalUsersToRemove = sectionUserMap[menuItem.id]?.map(u => `user-${u}`) + } + + const unsavedUsersToRemove = additionalUsersToRemove.filter(x => !savedAttendees?.includes(x)) + + const menuItemsToRemove = menuItemList.filter(u => unsavedUsersToRemove?.includes(u.assetCode)) + menuItemsToRemove.push(menuItem) const newSelectedMenuItems = selectedMenuItems - const removalIndex = newSelectedMenuItems.indexOf(menuItem) - if (removalIndex > -1) { - newSelectedMenuItems.splice(removalIndex, 1) - } + menuItemsToRemove.forEach(currentMenuItem => { + const removalIndex = newSelectedMenuItems.indexOf(currentMenuItem) + if (removalIndex > -1) { + newSelectedMenuItems.splice(removalIndex, 1) + } + }) setSelectedMenuItems([...newSelectedMenuItems]) onChange([...newSelectedMenuItems]) } const addSelectedItem = (event, {id}) => { - const menuItem = menuItemList.find(u => u.id === id) - + const menuItem = menuItemList.find(u => u.assetCode === id) // Exit if selected menu item is already selected if (selectedMenuItems.includes(menuItem)) { setIsOpen(false) @@ -118,8 +197,25 @@ export const ConferenceAddressBook = ({ return } - const newSelectedItems = selectedMenuItems - newSelectedItems.push(menuItem) + // Get users from group or section to add + let additionalUsersToAdd = [] + if (menuItem.type === 'group') { + additionalUsersToAdd = groupUserMap[menuItem.id] || [] + } else if (menuItem.type === 'section') { + additionalUsersToAdd = sectionUserMap[menuItem.id] || [] + } + additionalUsersToAdd = additionalUsersToAdd?.map(u => `user-${u}`) + // Remove users that have already been selected so duplicates do not occur + const selectedMenuItemsAssetCode = selectedMenuItems?.map(u => u.assetCode) + const unselectedUsers = additionalUsersToAdd.filter( + x => !selectedMenuItemsAssetCode.includes(x) + ) + const additionalUsersToAddMenuItem = menuItemList.filter(u => + unselectedUsers?.includes(u.assetCode) + ) + additionalUsersToAddMenuItem.push(menuItem) + + const newSelectedItems = selectedMenuItems.concat(additionalUsersToAddMenuItem) setAnnouncement(`${menuItem.displayName} selected. List collapsed.`) setSelectedMenuItems([...newSelectedItems]) onChange([...newSelectedItems]) @@ -139,7 +235,7 @@ export const ConferenceAddressBook = ({ return (
document.getElementById('flash-messages')} @@ -183,10 +323,9 @@ export const ConferenceAddressBook = ({ ConferenceAddressBook.propTypes = { menuItemList: PropTypes.array, - selectedIds: PropTypes.arrayOf(PropTypes.string), - savedAttendees: PropTypes.arrayOf(PropTypes.string), onChange: PropTypes.func, isEditing: PropTypes.bool, + selectedItems: PropTypes.arrayOf(PropTypes.object), } ConferenceAddressBook.defaultProps = { @@ -195,9 +334,9 @@ ConferenceAddressBook.defaultProps = { const ConferenceAddressBookTags = ({selectedMenuItems, onDismiss}) => { return selectedMenuItems.map((menuItem, index) => ( 0 ? 'xxx-small 0 xxx-small xx-small' : 'xxx-small 0'} diff --git a/ui/features/conferences/react/components/ConferenceAddressBook/__tests__/ConferenceAddressBook.test.js b/ui/features/conferences/react/components/ConferenceAddressBook/__tests__/ConferenceAddressBook.test.js index c32d1811313..e94d2842f23 100644 --- a/ui/features/conferences/react/components/ConferenceAddressBook/__tests__/ConferenceAddressBook.test.js +++ b/ui/features/conferences/react/components/ConferenceAddressBook/__tests__/ConferenceAddressBook.test.js @@ -21,14 +21,23 @@ import {fireEvent, render} from '@testing-library/react' import {ConferenceAddressBook} from '../ConferenceAddressBook' describe('ConferenceAddressBook', () => { + afterEach(() => { + window.ENV.sections = [] + window.ENV.section_user_ids_map = {} + window.ENV.groups = [] + window.ENV.group_user_ids_map = {} + }) + const menuItemList = [ - {displayName: 'Allison', id: '7'}, - {displayName: 'Caleb', id: '3'}, - {displayName: 'Chawn', id: '2'}, + {displayName: 'Allison', id: '7', type: 'user', assetCode: 'user-7'}, + {displayName: 'Caleb', id: '3', type: 'user', assetCode: 'user-3'}, + {displayName: 'Chawn', id: '2', type: 'user', assetCode: 'user-2'}, + {displayName: 'Group1', id: '23', type: 'group', assetCode: 'group-23'}, + {displayName: 'Section1', id: '24', type: 'section', assetCode: 'section-24'}, ] - const setup = (props = {}) => { - return render() + const setup = (props = {}, menuList = menuItemList) => { + return render() } it('should render', () => { @@ -62,12 +71,94 @@ describe('ConferenceAddressBook', () => { expect(tag).toBeTruthy() }) - it('should render initial tags when selectedIds is passed', () => { - const container = setup({selectedIds: ['2']}) + it('should add tag when group is selected', () => { + const container = setup() + const input = container.getByTestId('address-input') + input.click() + const item = container.getByText('Group1') + item.click() const tag = container.getByTestId('address-tag') expect(tag).toBeTruthy() }) + it('should add tag when section is selected', () => { + const container = setup() + const input = container.getByTestId('address-input') + input.click() + const item = container.getByText('Section1') + item.click() + const tag = container.getByTestId('address-tag') + expect(tag).toBeTruthy() + }) + + it('should have section header when section is present', () => { + const container = setup() + const input = container.getByTestId('address-input') + input.click() + const item = container.getByTestId('section-conference-header') + expect(item).toBeTruthy() + }) + + it('should have group header when group is present', () => { + const container = setup() + const input = container.getByTestId('address-input') + input.click() + const item = container.getByTestId('group-conference-header') + expect(item).toBeTruthy() + }) + + it('should not have group header when no groups exist', () => { + const menuItemList = [ + {displayName: 'Allison', id: '7', type: 'user', assetCode: 'user-7'}, + {displayName: 'Caleb', id: '3', type: 'user', assetCode: 'user-3'}, + {displayName: 'Chawn', id: '2', type: 'user', assetCode: 'user-2'}, + {displayName: 'Section1', id: '24', type: 'section', assetCode: 'section-24'}, + ] + const container = setup({}, menuItemList) + const input = container.getByTestId('address-input') + input.click() + const item = container.queryByTestId('group-conference-header') + expect(item).toBeFalsy() + }) + + it('should have User header', () => { + const container = setup() + const input = container.getByTestId('address-input') + input.click() + const item = container.getByTestId('user-conference-header') + expect(item).toBeTruthy() + }) + + it('should render initial tags when selectedIds is passed', () => { + const container = setup({ + selectedItems: [{displayName: 'Chawn', id: '2', type: 'user', assetCode: 'user-2'}], + }) + const tag = container.getByTestId('address-tag') + expect(tag).toBeTruthy() + }) + + it('should initially render groups that have all users selected', () => { + window.ENV.groups = [{displayName: 'Group1', id: '23'}] + window.ENV.group_user_ids_map = {23: ['2']} + const container = setup({ + selectedItems: [{displayName: 'Chawn', id: '2', type: 'user', assetCode: 'user-2'}], + }) + const tag = container.getAllByTestId('address-tag') + expect(tag).toBeTruthy() + expect(tag.length).toBe(2) + }) + + it('should initially render sections that have all users selected', () => { + window.ENV.sections = [{displayName: 'Section1', id: '24'}] + window.ENV.section_user_ids_map = {24: ['2']} + const container = setup({ + selectedItems: [{displayName: 'Chawn', id: '2', type: 'user', assetCode: 'user-2'}], + }) + const tag = container.getAllByTestId('address-tag') + expect(tag).toBeTruthy() + expect(tag.length).toBe(2) + }) + it('should remove selected user when backspace is pressed and input is empty', () => { const container = setup() const input = container.getByTestId('address-input') @@ -80,7 +171,7 @@ describe('ConferenceAddressBook', () => { }) it('should not remove saved users when isEditing', () => { - const container = setup({isEditing: true, savedAttendees: ['7']}) + const container = setup({isEditing: true, selectedItems: [menuItemList[0]]}) const input = container.getByTestId('address-input') input.click() const item = container.getByText('Allison') @@ -93,7 +184,7 @@ describe('ConferenceAddressBook', () => { }) it('should remove unsaved users when isEditing', () => { - const container = setup({isEditing: true, savedAttendees: ['25']}) + const container = setup({isEditing: true, selectedItems: []}) const input = container.getByTestId('address-input') input.click() const item = container.getByText('Allison') diff --git a/ui/features/conferences/react/components/VideoConferenceModal/VideoConferenceModal.js b/ui/features/conferences/react/components/VideoConferenceModal/VideoConferenceModal.js index b453d59a574..9cd3801a303 100644 --- a/ui/features/conferences/react/components/VideoConferenceModal/VideoConferenceModal.js +++ b/ui/features/conferences/react/components/VideoConferenceModal/VideoConferenceModal.js @@ -189,7 +189,6 @@ export const VideoConferenceModal = ({ onAttendeesChange={setSelectedAttendees} availableAttendeesList={availableAttendeesList} selectedAttendees={selectedAttendees} - savedAttendees={props.savedAttendees} showCalendar={showCalendarOptions} setAddToCalendar={setAddToCalendar} addToCalendar={addToCalendar} @@ -225,7 +224,6 @@ export const VideoConferenceModal = ({ onAttendeesChange={setSelectedAttendees} availableAttendeesList={availableAttendeesList} selectedAttendees={selectedAttendees} - savedAttendees={props.savedAttendees} nameValidationMessages={nameValidationMessages} descriptionValidationMessages={descriptionValidationMessages} hasBegun={props.hasBegun} @@ -337,8 +335,7 @@ VideoConferenceModal.propTypes = { attendeesOptions: PropTypes.arrayOf(PropTypes.string), type: PropTypes.string, availableAttendeesList: PropTypes.arrayOf(PropTypes.object), - selectedAttendees: PropTypes.arrayOf(PropTypes.string), - savedAttendees: PropTypes.arrayOf(PropTypes.string), + selectedAttendees: PropTypes.arrayOf(PropTypes.object), startCalendarDate: PropTypes.string, endCalendarDate: PropTypes.string, }