OfficeDev / microsoft-teams-emergency-operations-center

The Microsoft Teams Emergency Operations Center (TEOC) solution template leverages the power of the Microsoft 365 platform to centralize incident response, information sharing and field communications using powerful services like Microsoft Lists, SharePoint and more.
MIT License
95 stars 40 forks source link

Can we reorder the channel tab mainly to make the Notes tab the last one #283

Closed mvcsharepointdev closed 3 weeks ago

mvcsharepointdev commented 4 weeks ago

First thanks for providing this awesome Teams App, but we want to do this minor modification, where we want to reorder the General tabs and make the "Notes tab" at the end:-

LUhWhndr

I tried this method :-

//ReOrder General Channel Tabs

 public async reorderTabs(graph: any, teamGroupId: any, channelID: string, tabName: string) {
            try {
                //get URL for the tab
                const tabsGraphEndPoint = graphConfig.teamsGraphEndpoint + "/" + teamGroupId + graphConfig.channelsGraphEndpoint + "/" + channelID + graphConfig.tabsGraphEndpoint;

                const tabs = await this.getGraphData(tabsGraphEndPoint, graph)
                // Find the "Notes" tab
                const notesTab = tabs.value.find((tab: any) => tab.displayName === "Notes");

                if (notesTab) 
                    {
                      // Update the orderHint to move the "Notes" tab to the end
                         const updateTabEndpoint = graphConfig.teamsGraphEndpoint + "/" + teamGroupId + graphConfig.channelsGraphEndpoint + "/" + channelID + graphConfig.tabsGraphEndpoint + "/"+ notesTab.id;
                         const lastOrderHint = tabs.value[tabs.value.length - 1].orderHint;

                         // Set the orderHint to something greater than the last tab's orderHint
                         const newOrderHint = "6";

                         await this.sendGraphPatchRequest(updateTabEndpoint, graph, {
                                    sortOrderIndex: newOrderHint
                    });
                    return notesTab.displayName;
                    }
            } catch (error) {
                console.error(
                    constants.errorLogPrefix + "CommonService_getTabURL \n",
                    JSON.stringify(error)
                );
                throw error;
            }
        }

while get called from : -

// wrapper method to perform teams related operations
    private async createTeamAndChannels(incidentId: any) {
        try {
            console.log(constants.infoLogPrefix + "M365 group creation starts");
            // call method to create Teams group
            const groupInfo = await this.createTeamGroup(incidentId);
            try {
                console.log(constants.infoLogPrefix + "M365 group created");

                // create associated team with the group
                const teamInfo = await this.createTeam(groupInfo);
                if (teamInfo.status) {
                    //log trace
                    this.dataService.trackTrace(this.props.appInsights, "Incident Team created ", incidentId, this.props.userPrincipalName);

                    //Send invitations to the guest users
                    let returnInvitationObj: any;
                    if (this.state.toggleGuestUsers)
                        returnInvitationObj = this.sendInvitation(groupInfo.id, teamInfo.data.displayName, teamInfo.data.webUrl)

                    // create channels
                    await this.createChannels(teamInfo.data);

                    this.setState({ loaderMessage: this.props.localeStrings.createPlanloaderMessage });

                    //Get General channel id
                    const generalChannelId = await this.dataService.getChannelId(this.props.graph,
                        groupInfo.id, constants.General);

                    //Add TEOC app to the Incident Team General channel's Active Dashboard Tab
                    await this.dataService.createActiveDashboardTab(this.props.graph, groupInfo.id,
                        generalChannelId, this.props.graphContextURL, this.props.appSettings);

                    //Create planner with the Group ID                        
                    const planID = await this.dataService.createPlannerPlan(groupInfo.id, incidentId, this.props.graph,
                        this.props.graphContextURL, this.props.tenantID, generalChannelId, false);

                    //added for GCCH tenant
                    if (this.props.graphBaseUrl !== constants.defaultGraphBaseURL) {
                        // wait for 5 seconds to ensure the SharePoint site is available via graph API
                        await this.timeout(5000);
                    }

                    // graph endpoint to get team site Id
                    const teamSiteURLGraphEndpoint = graphConfig.teamGroupsGraphEndpoint + "/" +
                        groupInfo.id + graphConfig.rootSiteGraphEndpoint;

                    // retrieve team site details
                    const teamSiteDetails = await this.dataService.getGraphData(teamSiteURLGraphEndpoint, this.props.graph);

                    //get the team site managed path
                    const teamSiteManagedPathURL = teamSiteDetails.webUrl.split(teamSiteDetails.siteCollection.hostname)[1];
                    console.log(constants.infoLogPrefix + "Site ManagedPath", teamSiteManagedPathURL);

                    // create news channel and tab
                    const newsTabLink = await this.createNewsTab(groupInfo, teamSiteDetails.webUrl, teamSiteManagedPathURL,groupInfo.id);

                    // create assessment channel and tab
                   // await this.createAssessmentChannelAndTab(groupInfo.id, teamSiteDetails.webUrl, teamSiteManagedPathURL);

                    // call method to create assessment list
                    //await this.createAssessmentList(groupInfo.mailNickname, teamSiteDetails.id);
                    //Reorder Tabs
                    const tabname = await this.dataService.reorderTabs(this.props.graph,teamInfo.data.id,generalChannelId,"Notes");
                    console.log(constants.infoLogPrefix + "Reorder General channel Tabs");
                    //log trace
                    this.dataService.trackTrace(this.props.appInsights, "Assessment list created ", incidentId, this.props.userPrincipalName);

                    //change the M365 group visibility to Private for GCCH tenant
                    if (this.props.graphBaseUrl !== constants.defaultGraphBaseURL) {
                        this.graphEndpoint = graphConfig.teamGroupsGraphEndpoint + "/" + groupInfo.id;
                        await this.dataService.sendGraphPatchRequest(this.graphEndpoint, this.props.graph, { "visibility": "Private" })
                        console.log(constants.infoLogPrefix + "Group setting changed to Private");
                    }

                    //Update Team details, Plan ID, NewsTabLink in Incident Transation List                                   
                    const updateItemObj = {
                        IncidentId: incidentId,
                        TeamWebURL: teamInfo.data.webUrl,
                        PlanID: planID,
                        NewsTabLink: newsTabLink
                    };

                    await this.updateIncidentItemInList(incidentId, updateItemObj);
                    console.log(constants.infoLogPrefix + "List item updated");

                    let roles: any = this.state.roleAssignments;
                    roles.push({
                        role: constants.incidentCommanderRoleName,
                        userNamesString: this.state.incDetailsItem.incidentCommander.userName,
                        userDetailsObj: [this.state.incDetailsItem.incidentCommander]
                    });

                    //post incident message in General Channel
                    await this.postIncidentMessage(groupInfo.id);

                    // create the tags for incident commander and each selected roles                        
                    await this.createTagObject(teamInfo.data.id, roles);
                    //log trace
                    this.dataService.trackTrace(this.props.appInsights, "Tags are created ", incidentId, this.props.userPrincipalName);
                    Promise.allSettled([returnInvitationObj]).then((promiseObj: any) => {
                        this.setState({
                            showLoader: false,
                            formOpacity: 1
                        });

                        // Display success message if incident updated successfully
                        this.props.showMessageBar(this.props.localeStrings.incidentCreationSuccessMessage, constants.messageBarType.success);

                        // Display error message if guest invitations
                        if ((promiseObj[0]?.value !== undefined && !promiseObj[0].value?.isAllSucceeded))
                            this.props.showMessageBar(
                                ((promiseObj[0]?.value !== undefined && !promiseObj[0]?.value?.isAllSucceeded) ? " " + promiseObj[0]?.value?.message + ". " : ""),
                                constants.messageBarType.error);
                        this.props.onBackClick(constants.messageBarType.success);
                    });

                }
                else {
                    // delete the group if some error occured
                    await this.deleteTeamGroup(groupInfo.id);
                    // delete the item if error occured
                    await this.deleteIncident(incidentId);

                    this.setState({
                        showLoader: false,
                        formOpacity: 1
                    })
                    this.props.showMessageBar(this.props.localeStrings.genericErrorMessage + ", " + this.props.localeStrings.errMsgForCreateIncident, constants.messageBarType.error);
                }
            }
            catch (error) {
                console.error(
                    constants.errorLogPrefix + "CreateIncident_createTeamAndChannels \n",
                    JSON.stringify(error)
                );
                // Log Exception
                this.dataService.trackException(this.props.appInsights, error, constants.componentNames.IncidentDetailsComponent, 'CreateIncident_createTeamAndChannels', this.props.userPrincipalName);
                // delete the group if some error occured
                await this.deleteTeamGroup(groupInfo.id);
                // delete the item if error occured
                await this.deleteIncident(incidentId);

                this.setState({
                    showLoader: false,
                    formOpacity: 1
                })
                this.props.showMessageBar(this.props.localeStrings.genericErrorMessage + ", " + this.props.localeStrings.errMsgForCreateIncident, constants.messageBarType.error);
            }
        }
        catch (error: any) {
            console.error(
                constants.errorLogPrefix + "CreateIncident_createTeamAndChannels \n",
                JSON.stringify(error)
            );
            // Log Exception
            this.dataService.trackException(this.props.appInsights, error, constants.componentNames.IncidentDetailsComponent, 'CreateIncident_createTeamAndChannels', this.props.userPrincipalName);

            // delete the item if error occured
            this.deleteIncident(incidentId);

            this.setState({
                showLoader: false,
                formOpacity: 1
            });

            // Display error message if M365 group creation fails with access denied error
            if (error?.statusCode === 403 && error?.code === constants.authorizationRequestDenied
                && error?.message === constants.groupCreationAccessDeniedErrorMessage) {
                this.props.showMessageBar(this.props.localeStrings.genericErrorMessage + ", " + this.props.localeStrings.m365GroupCreationFailedMessage, constants.messageBarType.error);
            }
            /* Display error message if M365 group creation fails with group already exists error 
            or any other error */
            else {
                this.props.showMessageBar(this.props.localeStrings.genericErrorMessage + ", " + this.props.localeStrings.errMsgForCreateIncident, constants.messageBarType.error);
            }
        }
    }

But after calling the above method during the site and channel creation the Notes will stay the 3rd tab. any advice?

v-saikirang commented 3 weeks ago

Hi @mvcsharepointdev,

Thanks for reaching out to us.

Please note that we do not support customization scenarios. Sorry, we will not be able to comment/advise on the custom code you have written. As of now with the standard version, you will not be able toc control the order of the default tabs.

Thanks.