How to build a Gantt chart into Microsoft Teams with Bryntum

Looking for a way to streamline your team’s project management process? Look no further! A Gantt chart is a powerful tool for visualizing and managing project timelines and it’s especially useful for teams working on complex projects: Team members can clearly see the progress of a project, identify potential roadblocks, and make necessary adjustments in real-time.
Integrating a Gantt chart into your team’s workflow in your Microsoft Teams account changes the game. Your team has easy access to the most up-to-date project information along with the collaboration tools of Microsoft Teams. No more relying on outdated spreadsheets and confusing project-management software.
In this tutorial, we’ll guide you through the process of setting up your own Bryntum Gantt application in Microsoft Teams and show you how to customize it to meet the specific needs of your team.
What you’ll need
We will work from starter code hosted on GitHub to create our app. You’ll need a few other tools too:
- Visual Studio Code (VS Code)
- Teams Toolkit, a Microsoft Visual Studio Code extension that creates a project scaffolding for your app
- Node.js
- Microsoft Teams
- Microsoft Edge or Google Chrome
Set up a new blank app in Teams
To create a new blank app in Teams, you’ll need to first install the Microsoft Teams Toolkit. The Teams Toolkit provides several useful tools that help in the app development process, such as tools for provisioning and deployment of cloud resources and the ability to publish the app to the Teams store.
Install Teams Toolkit
Open VS Code and navigate to the Extensions tab.
Search for “Teams Toolkit” and select “Install” next to it.
Once installed, you’ll see the Teams Toolkit icon appear in the VS Code Activity Bar.
Check that sideloading is enabled
We will use the Teams Toolkit for previewing and testing our app, a process known as sideloading. You will need to have a Microsoft tenant with admin access or a Microsoft Developer account to proceed.
To check whether your account has a tenant with admin access, follow these steps:
1. Log in to Teams, select the Store icon, select “Manage your apps”, then select “Upload an app”.
2. If you see the option to “Upload a custom app”, then you can test and preview your app with this account.
If you do not have admin access, sign up for the Microsoft 365 developer program by following these steps:
1. Go to the Microsoft 365 Developer Program.
2. Select “Join Now” and follow the onscreen instructions.
3. In the welcome screen, select “Set up E5 subscription”.
4. Set up your administrator account. When you’re finished, you will be given a new email address (seen under “Administrator”) that you can use to log in to Teams with admin access to a tenant with sideloading enabled.
Now log in to the Teams Toolkit on VS Code with your admin account.
Clone starter code
The Teams Toolkit extension provides a number of template applications that you can experiment with.
To make setting up our project a little easier, clone the starter code for our Teams app here and open it in VS Code.
The starter code is a Teams tab template app with some extra folders already set up so we don’t have to do that in this tutorial.
There are a lot of files in our directory, most of which contain configuration for our tab app. We will focus on the files in the tab
folder.
Write the frontend code (Bryntum)
To install Bryntum Gantt, open a terminal and navigate to the tabs
folder with `cd tabs`.
Access the npm registry by following this step on the Bryntum website.
Now install a TypeScript Gantt template by running the following command from the tabs
folder:
npx create-react-app my-app --template @bryntum/cra-template-typescript-gantt
You can preview the app by running these three commands from the terminal:
cd my-app
npm install
npm run start
You will be able to see the Gantt under http://localhost:3000. Your browser should automatically open it for you.
We will not be using this template in this tutorial. We have updated the package.json
file in the tabs
folder to include the dependencies we need for a Bryntum Gantt. If you open up package.json
in the tabs
folder, you will see the following under the dependencies
section:
"@bryntum/gantt": "npm:@bryntum/gantt-trial@5.2.8",
"@bryntum/gantt-react": "5.2.8",
And the following in the devDependencies
section:
"babel-preset-react-app": "npm:@bryntum/babel-preset-react-app@10.0.0",
Compare these lines with the lines in the same sections of package.json
in the my-app
folder. If the lines differ, go ahead and replace these lines in the tabs
version with the ones you have just installed as they will be the version you specified in your installation.
Now cd
to your tabs
folder and run `npm i` if you made any changes to package.json
there. You should now have access to the Bryntum Gantt in your Teams tab starter code and we can get to building.
Set up the Gantt configuration
Navigate to the Gantt
folder (find it in tabs/src/components/
) and create a new file called GanttCofig.tsx
. Paste the following code into the file:
import { GanttConfig, ProjectModel } from '@bryntum/gantt';
const project = new ProjectModel({
transport: {
load: {
url: 'data/gantt-data.json',
},
},
autoLoad: true,
});
const ganttConfig: Partial<GanttConfig> = {
width: '100vw',
height: '100vh',
dependencyIdField: 'sequenceNumber',
columns: [{ type: 'name', width: 250, text: 'Tasks' }],
viewPreset: 'weekAndDayLetter',
barMargin: 10,
project,
};
project.load();
export { ganttConfig };
Here we set up the configuration for our Gantt chart. We start by importing the ProjectModel
and GanttConfig
objects from the @bryntum/gantt
library.
Next we create an instance of the ProjectModel
and configure it with a transport
, which is used to specify the location of the data that will be used to populate the chart (in this case, the data is located in a file called gantt-data.json
). The autoLoad
property is set to true
so that the data will be automatically loaded when the project is created.
Then we define the GanttConfig
. This will help configure our Gantt chart with properties like the width and height of the chart, the field that will be used to identify dependencies between tasks, the columns that will be displayed in the chart, the view preset to be used (weekAndDayLetter), the margin between the bars, and the project object that was created earlier.
Finally, the load()
method of the project object is called to load the data and create the chart. The ganttConfig
object is then exported so that it can be used in other parts of the application. We will use ganttConfig
to set up our final Gantt chart and to adjust its readOnly
property depending on whether a logged-in Teams user has permission to edit the Gantt chart.
Create the Gantt component
Now create a new file called Gantt.tsx
in the Gantt
folder and paste the following code into it:
import React, { useRef, useEffect } from 'react';
import { BryntumGantt } from '@bryntum/gantt-react';
import { ganttConfig } from './GanttConfig';
import '../../styles/App.scss';
interface Props<T> {
role: number;
}
export default function Gantt<T extends unknown>({ role }: Props<T>) {
const gantt = useRef<BryntumGantt>(null);
useEffect(() => {
if (role === 0) {
ganttConfig.readOnly = true;
} else {
ganttConfig.readOnly = false;
}
}, [role]);
return <BryntumGantt ref={gantt} {...ganttConfig} />;
}
Here we create a React component that renders a Gantt chart using the Bryntum Gantt library for React.
First we make some imports from the React library and import the Bryntum Gantt library, the ganttConfig
we created in the previous step, and some styling (which we will create in the next step).
We define the React component as a functional component with a generic type T
and an interface Props
with a role
property. We then define a BryntumGantt
component using the useRef
hook and use the useEffect
hook to listen to changes on the role
prop.
The useEffect
hook runs a callback function that checks the value of the role
property. If the value is 0
, it makes the Gantt read-only by assigning `ganttConfig.readOnly = true`, else it assigns `ganttConfig.readOnly = false`. This allows us to update the Gantt’s readOnly
property to give a user editing permission based on their role in the Teams environment.
Finally, the BryntumGantt
component is returned, configured with ganttConfig
.
To make the Gantt
component accessible throughout your project, create a file called index.tsx
in the Gantt
folder and paste the following code into it:
import Gantt from './Gantt';
export default Gantt;
We will use the Bryntum Stockholm theme to style our Gantt. Navigate to the styles
folder in tabs/src/styles/
and create a new file called App.scss
. Paste the following code into the new file:
// import bryntum theme
@import '~@bryntum/gantt/gantt.stockholm.css';
body,
html {
margin: 0;
display: flex;
flex-direction: column;
height: 100vh;
font-family: Poppins, 'Open Sans', Helvetica, Arial, sans-serif;
font-size: 14px;
}
#container {
flex: 1 1 100%;
}
This theme is required to render the Bryntum Gantt correctly. Follow this link for more information on styling your Bryntum Gantt.
Access user’s Microsoft details with TeamsFX
We need to access the logged-in Teams user’s data in order to determine their role. We will use this information to turn off edit access to our Gantt for users who don’t have the necessary permissions.
First, create a file called AcmeGantt.tsx
in the components
folder. You can rename this file to match your company or app name, but remember to keep a lookout for imports in the tutorial code and update them accordingly.
Next, paste the following code into the AcmeGantt.tsx
file:
import { Providers, ProviderState } from '@microsoft/mgt-element';
import { TeamsFxProvider } from '@microsoft/mgt-teamsfx-provider';
import { useGraph } from '@microsoft/teamsfx-react';
import React, { useState, useContext, useEffect } from 'react';
import { TeamsFxContext } from './Context';
import Gantt from '../components/Gantt';
export default function AcmeGantt() {
const [role, setRole] = useState(0);
return (
<div>
<Gantt role={role} />
</div>
);
}
Here we make some imports necessary for accessing the user’s Teams data, like the @microsoft/mgt-element
and @microsoft/mgt-teamsfx-provider
packages and the useGraph
function from @microsoft/teamsfx-react
.
Next we define our application AcmeGantt
and declare a state variable called role
(with an initial value of 0
) using the useState
hook. This role
will be set using setRole
after we have accessed the user’s data in the next step.
To get access to the user’s Teams data, paste the following code above the return
of the AcmeGantt
component:
const { teamsfx } = useContext(TeamsFxContext);
const dataGraph = useGraph(
async (graph, teamsfx, scope) => {
// Call graph api directly to get user profile information
const profile = await graph.api('/me').get();
// Initialize Graph Toolkit TeamsFx provider
const provider = new TeamsFxProvider(teamsfx, scope);
Providers.globalProvider = provider;
Providers.globalProvider.setState(ProviderState.SignedIn);
return { profile };
},
{ scope: ['User.Read'], teamsfx: teamsfx }
);
useEffect(() => {
dataGraph.reload();
}, []);
Here we define a useGraph
hook that will retrieve data from the Microsoft Graph API.
The useGraph
hook accepts a callback function that is executed when the component is rendered. Inside the callback function, it makes a direct call to the Microsoft Graph API to get the user’s profile information and assigns the response to the profile
variable.
We then define a TeamsFxProvider
class to provide authentication and API access to the Microsoft Graph API for Teams-specific functionality.
The callback function returns an object containing the profile
variable with our app user’s Microsoft Teams data.
Finally, we define a useEffect
hook to call the reload
method of dataGraph
when the component is rendered.
Now we need to render the AcmeGantt
component again so that we can use the user’s Teams data to determine the readOnly
configuration of the Bryntum Gantt chart. Paste the following code above the return
of the AcmeGantt
component:
useEffect(() => {
let managerRole = 0;
if (dataGraph.data) {
if (dataGraph.data?.profile.jobTitle === 'Manager') {
managerRole = 1;
}
setRole(managerRole);
}
}, [dataGraph.data, setRole]);
Here we define a useEffect
hook to run some logic whenever the component is rendered. The hook takes in the user data we received from dataGraph
and the setRole
function to update the role
variable of our Gantt based on this data.
In this example, we will check if the user has a jobTitle
of 'Manager'
in their Microsoft Teams account. We will enable edit access if they do and restrict edit access if not. You can check the returned data and determine for yourself what data you would like to base these permissions on.
The useEffect
hook declares a variable called managerRole
, which is initially set to the value of 0
. It then checks if the dataGraph.data
variable exists. If it does, it checks if the jobTitle
property of the profile
object in dataGraph.data
is equal to 'Manager'
. If it is, it sets the managerRole
variable to 1
. This will trigger the setRole
function, which will rerender the AcmeGantt
component with an updated Gantt
configuration.
Run your app
To run your app, you need to import it into the Tab.tsx
file, which can be found in the pages
folder.
To import your app, paste the following import at the top of the file (make sure to adjust the import based on your app’s name):
import AcmeGantt from '../components/AcmeGantt';
Then replace this comment:
{
/* <Your app name here /> */
}
With an instance of your app:
<AcmeGantt />
Finally, navigate to the Teams Toolkit extension in VS Code and click “Preview your Teams app” in the “Development” section.
The app will start up your browser. Once you’ve logged in to Microsoft Teams, you will be prompted to add your app to your Microsoft Teams account. Click “Add”.
Your Teams tab app has been added to your account in testing mode and your Bryntum Gantt is rendering. If you cannot edit your Gantt, it may be because your profile does not have the jobTitle
of manager at the moment. No worries, to fix this for development, find this line of code in AcmeGantt.tsx
:
<Gantt role={role} />
And change it to:
<Gantt role={1} />
This will configure the app with edit access the next time you reload the page or app.
Save changes
Now you may notice that after a page refresh, any changes you make to your Gantt are reset. This is because the project
data is not currently being stored and updated in a database. In the next section, we will configure our Gantt to communicate with a server that will update our tasks stored in a SQL database.
Write the backend code (API Bryntum)
To get our data from another source, we need to update the Gantt configuration file. Navigate to ganttConfig.tsx
and replace the project
definition with the following code:
const project = new ProjectModel({
taskStore: {
autoTree: true,
transformFlatData: true,
},
// specify data source
transport: {
load: {
url: 'http://localhost:8010/data',
},
sync: {
url: 'http://localhost:8010/api',
},
},
autoLoad: true,
autoSync: true,
validateResponse: true,
});
Here we have recreated the ProjectModel
with new configuration.
First we create a taskStore
for the project and set autoTree
and transformFlatData
to true
to automatically transform the formatting of our data.
Next, the transport
options are set to load data from a specified URL. This URL will be a route on the server we create in the next step. We use sync
to specify the URL to which the project should send data when it is synced.
Finally, we set the autoLoad
, autoSync
, and validateResponse
options to true
to automatically load data, sync data, and validate responses from our server.
Now our Gantt is configured to fetch data from a server at http://localhost:8010/
.
Create the server
Navigate to the server
folder in the project’s root and run `npm install` in your terminal to install the packages we need to create our server.
Next, create a file called server.js
in the server
folder and paste the following code into it:
const express = require('express');
const bodyParser = require('body-parser');
const uuid = require('uuid');
const cors = require('cors');
require('dotenv').config();
const app = express();
const port = process.env.PORT || 8010;
app.use(
cors({
origin: 'https://localhost:53000',
credentials: true,
})
);
app.disable('x-powered-by');
app.use(bodyParser.json());
app.listen(port, () => {
console.log('Server is running on port ' + port + '...');
});
Here we set up our Express server with some configuration for communicating with our Teams app. We use bodyParser
because Bryntum sends request objects in JSON format. We import uuid
to generate unique IDs and enable CORS (Cross-Origin Resource Sharing). We use app.disable('x-powered-by');
to hide the server’s technology stack from potential attackers.
Create the CRUD functions
Now we need to create the functions that will perform CRUD operations on our database and define the server routes those functions will use to interact with our database.
First define the serverConfig
function, which will run when the server is started, to connect to the MySQL database. The serverConfig
function has a helper function called sendResponse
that formats the response of create, update, or delete operations from the Bryntum Gantt chart. The response format required is described in the Bryntum Gantt chart docs.
const mysql = require('mysql2/promise');
const db = mysql.createPool({
host: process.env.HOST,
user: process.env.MYSQL_USER,
password: process.env.PASSWORD,
database: process.env.DATABASE,
});
async function serverСonfig() {
function sendResponse(
res,
action,
requestId,
error,
taskUpdates,
dependencyUpdates,
tasksRemoved,
dependenciesRemoved
) {
if (action == 'error') console.log(error);
let result = {
success: action === 'error' ? false : true,
};
if (requestId !== undefined && requestId !== null)
result.requestId = requestId;
// Operations to be added
res.send(result);
return;
}
}
serverСonfig();
Now create a .env
file in the root folder and add the following lines for connecting to a MySQL database:
HOST=localhost
MYSQL_USER=
PASSWORD=
DATABASE=bryntum
PORT=8010
Add the root password and username for your MySQL server.
Load data from MySQL to the Bryntum Gantt chart
To get data from MySQL to our Bryntum Gantt chart, add the following GET method /data
route to server.js
, above the sendResponse
function:
// Read
app.get('/data', async (req, res) => {
try {
const results = await Promise.all([
db.query('SELECT * FROM tasks'),
db.query('SELECT * FROM dependencies'),
]);
const tasks = results[0][0],
dependencies = results[1][0];
res.send({
success: true,
tasks: {
rows: tasks,
},
dependencies: {
rows: dependencies,
},
});
} catch (error) {
sendResponse(res, 'error', null, error, [], [], [], []);
}
});
We defined this GET method route in our ganttConfig
, which will load data from this /data
route.
Updating tasks and dependencies
In the ganttConfig.tsx
file we created earlier, we set the sync
URL of our project’s transport
to http://localhost:8010/api
. This is the route our app will send task update data to, so it needs to handle new, deleted, and updated tasks and dependencies.
To handle sync requests from our Bryntum Gantt chart, add the following method in the serverConfig
function above the sendResponse
function:
// Create, Update, Delete (Tasks & Dependencies)
app.post('/api', async function (req, res) {
const db = mysql.createPool({
host: process.env.HOST,
user: process.env.MYSQL_USER,
password: process.env.PASSWORD,
database: process.env.DATABASE,
});
let requestId = '';
let lastKey = '';
let err = null;
const taskUpdates = [];
const tasksRemoved = [];
const dependencyUpdates = [];
const dependenciesRemoved = [];
console.log(
`\n${req.method} ${req.url} --> ${JSON.stringify(req.body, `\t`, 2)}`
);
for (const [key, value] of Object.entries(req.body)) {
if (key === 'requestId') {
requestId = value;
}
if (key === 'tasks') {
for (const [key2, value2] of Object.entries(value)) {
if (key2 === 'added') {
value2.forEach((addObj) => taskUpdates.push(addObj));
value2[0].id = uuid.v4();
const val = await createOperation(value2[0], 'tasks');
lastKey = val.msg;
err = val.error;
}
if (key2 === 'updated') {
console.log(`updated tasks...`, value2);
value2.forEach((updateObj) => taskUpdates.push(updateObj));
const val = await updateOperation(value2, 'tasks');
lastKey = val.msg;
err = val.error;
}
if (key2 === 'removed') {
tasksRemoved.push(value2[0]);
const val = await deleteOperation(value2[0].id, 'tasks');
lastKey = val.msg;
err = val.error;
}
}
}
if (key === 'dependencies') {
for (const [key2, value2] of Object.entries(value)) {
if (key2 === 'added') {
value2[0].id = uuid.v4();
value2.forEach((addObj) => dependencyUpdates.push(addObj));
const val = await createOperation(value2[0], 'dependencies');
lastKey = val.msg;
err = val.error;
}
if (key2 === 'updated') {
value2.forEach((updateObj) => dependencyUpdates.push(updateObj));
const val = await updateOperation(value2, 'dependencies');
lastKey = val.msg;
err = val.error;
}
if (key2 === 'removed') {
dependenciesRemoved.push(value2[0]);
const val = await deleteOperation(value2[0].id, 'dependencies');
lastKey = val.msg;
err = val.error;
}
}
}
}
sendResponse(
res,
lastKey,
requestId,
err,
taskUpdates,
dependencyUpdates,
tasksRemoved,
dependenciesRemoved
);
});
When changes are made to our Gantt, the client-side Bryntum library will make a POST request to the /api
endpoint to keep the data in the database in sync with the client-side UI. The code we added will handle the incoming POST request and inspect the request body to determine what action should be taken on which data model. We first determine which data model to apply a sync request to and then inspect the type of operation to perform.
For example, when adding a task to the database, there will be a POST request to the /api
route. The route will check if the sync request body object has a key called tasks
and then check if the nested object key is an operation called added
. If this is the case for both, it will call the createOperation
function (which we will define later on) and insert the added task into our database using the MySQL client. Then we insert the relevant columns to be stored and ignore keys of the object that we don’t need.
A similar process is followed for updating and removing tasks and for adding, updating, and removing dependencies.
Now let’s add the createOperation
, updateOperation
, and deleteOperation
functions to handle the CRUD functionality of our server.
Update function
Add the following code at the bottom of server.js
:
async function updateOperation(updates, table) {
try {
await Promise.all(
updates.map(({ id, ...update }) => {
return db.query(
`
UPDATE ${table}
SET ${Object.keys(update)
.map((key) => `${key} = ?`)
.join(', ')}
WHERE id = ?
`,
Object.values(update).concat(id)
);
})
);
return { msg: 'update', error: null };
} catch (error) {
return { msg: 'error', error };
}
}
There may be multiple updates to perform at once, so we loop through the update
property array and create key and value arrays for each update. We use these to construct UPDATE
database queries. The Promise.all
method from the bluebird library handles all of the async updates at once. The updateOperation
function returns a result object. The response is sent using the sendResponse
method, which formats the response correctly. The table
argument allows us to add tasks to either the tasks
table or the dependencies
table.
Add function
Now add the following function above the updateOperation
function:
async function createOperation(addObj, table) {
const valArr = [];
const keyArr = [];
for (const [key, value] of Object.entries(addObj)) {
if (
key !== 'baselines' &&
key !== 'from' &&
key !== 'to' &&
key !== '$PhantomId' &&
key !== 'segments' &&
key !== 'ignoreResourceCalendar'
) {
keyArr.push(`\`${key}\``);
valArr.push(value);
}
}
try {
await db.query(
`INSERT INTO ${table} (${keyArr.join(', ')}) VALUES (${Array(
keyArr.length
)
.fill('?')
.join(',')})`,
valArr
);
return { msg: 'added', error: null };
} catch (error) {
return { msg: 'error', error: error };
}
}
Delete function
Now add the following deleteOperation
function below the updateOperation
function:
async function deleteOperation(id, table) {
try {
await db.query(`DELETE FROM ${table} WHERE id = ?`, [id]);
return { msg: 'deleted', error: null };
} catch (error) {
return { msg: 'error', error: error };
}
}
We use the id
argument to find the task and delete it using the DELETE
MySQL query.
Update the `sendResponse` function
We can now update our sendResponse
function to include the operations we added for tasks and dependencies:
function sendResponse(
res,
action,
requestId,
error,
taskUpdates,
dependencyUpdates,
tasksRemoved,
dependenciesRemoved
) {
if (action == 'error') console.log(error);
const result = {
success: action === 'error' ? false : true,
};
if (requestId !== undefined && requestId !== null)
result.requestId = requestId;
// updated tasks
result.tasks = {};
result.tasks.rows = [];
if (taskUpdates.length) {
result.tasks.rows = [...result.tasks.rows, ...taskUpdates];
}
// deleted tasks
result.tasks.removed = [];
if (tasksRemoved.length) {
result.tasks.removed = [...result.tasks.removed, ...tasksRemoved];
}
// updated dependencies
result.dependencies = {};
result.dependencies.rows = [];
if (dependencyUpdates.length) {
result.dependencies.rows = [
...result.dependencies.rows,
...dependencyUpdates,
];
}
// deleted dependencies
result.dependencies.removed = [];
if (dependenciesRemoved.length) {
result.dependencies.removed = [
...result.dependencies.removed,
...dependenciesRemoved,
];
}
res.send(result);
return;
}
In this function, we format the response to include the results of the operations applied and send the response to the client to complete the handling of the HTTP request.
The API endpoints have now been successfully created to be compatible with our implementation of the Bryntum Gantt chart.
Configure the MySQL database
To set up a MySQL database locally, we’ll install MySQL Server and MySQL Workbench. MySQL Workbench is a MySQL GUI that we’ll use to create a database with tables for the Gantt data and to run queries.
Download MySQL Server and MySQL Workbench from the MySQL community downloads page. If you’re using Windows, you can use the MySQL Installer to download MySQL products. Use the default configurations when configuring MySQL Server and Workbench. Make sure that you configure the MySQL Server to start at system startup for convenience.
Open the MySQL Workbench desktop application. Open the local instance of the MySQL Server that you configured.
We’ll write our MySQL queries in the query tab and execute the queries by pressing the yellow lightning bolt button.
Let’s run some MySQL queries in MySQL Workbench to create, use, and populate a database for our Bryntum Gantt.
Run the following query in MySQL Workbench to create a database called bryntum
:
CREATE DATABASE bryntum;
Now run the following query to set the bryntum
database for use:
USE bryntum;
Let’s create the two tables we need for our Bryntum Gantt chart data: tasks
and dependencies
.
Run the following query to create the tasks
table for our task data:
CREATE TABLE `tasks` (
`id2` varchar(80) NOT NULL,
`parentId` varchar(80) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`startDate` datetime DEFAULT NULL,
`endDate` datetime DEFAULT NULL,
`effort` float(11,2) DEFAULT NULL,
`effortUnit` varchar(255) DEFAULT 'hour',
`duration` float(11,2) unsigned DEFAULT NULL,
`durationUnit` varchar(255) DEFAULT 'day',
`percentDone` float(11,2) unsigned DEFAULT '0.00',
`schedulingMode` varchar(255) DEFAULT NULL,
`note` text,
`constraintType` varchar(255) DEFAULT NULL,
`constraintDate` datetime DEFAULT NULL,
`manuallyScheduled` tinyint DEFAULT '1',
`effortDriven` tinyint DEFAULT '0',
`inactive` tinyint DEFAULT '0',
`cls` varchar(255) DEFAULT NULL,
`iconCls` varchar(255) DEFAULT NULL,
`color` varchar(255) DEFAULT NULL,
`parentIndex` int DEFAULT '0',
`expanded` tinyint DEFAULT '0',
`calendar` int DEFAULT NULL,
`deadline` datetime DEFAULT NULL,
`direction` varchar(255) DEFAULT NULL,
`$PhantomId` varchar(255) DEFAULT NULL,
`unscheduled` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `parentId` (`parentId`),
KEY `calendar` (`calendar`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Run the following query to create the dependencies
table for our dependency data:
CREATE TABLE `dependencies` (
`id` varchar(80) NOT NULL,
`fromEvent` varchar(80) DEFAULT NULL,
`toEvent` varchar(80) DEFAULT NULL,
`type` int DEFAULT '2',
`cls` varchar(255) DEFAULT NULL,
`lag` float(11,2) DEFAULT '0.00',
`lagUnit` varchar(255) DEFAULT 'day',
`$PhantomId` varchar(255) DEFAULT NULL,
`active` tinyint(1) DEFAULT NULL,
`fromSide` varchar(255) DEFAULT NULL,
`toSide` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fromEvent` (`fromEvent`),
KEY `toEvent` (`toEvent`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
Now add some example tasks data to the tasks
table:
INSERT INTO `tasks` (`id`, `name`, `expanded`, `iconCls`, `percentDone`, `startDate`, `endDate`, `parentId`, `effort`, `duration`, `parentIndex`) VALUES
("1000", 'Launch SaaS Product', 1, '', 34.248366013071895, '2022-11-14 00:00:00', '2022-11-29 00:00:00', NULL, 153.00, 15.00, 0),
("1", 'Setup web server', 1, '', 42.30769230769231, '2022-11-14 00:00:00', '2022-11-29 00:00:00', "1000", 13.00, 15.00, 0),
("11", 'Install Apache', 0, '', 50.00, '2022-11-14 00:00:00', '2022-11-17 00:00:00', "1", 3.00, 3.00, 0),
("12", 'Configure firewall', 0, '', 50.00, '2022-11-18 00:00:00', '2022-11-29 00:00:00', "1", 3.00, 11.00, 1);
We can also add some example dependencies data:
INSERT INTO `dependencies` (`id`, `fromEvent`, `toEvent`, `lag`) VALUES
("1","11","12",2);
Run your app
Now that our database, server, and Teams app is set up, we can run our application with data persistence. Navigate to the server
folder in your terminal and run:
npm install
npm run start
Next, open up another terminal, navigate to the tabs
folder, and run:
npm install
Now navigate to the Teams Toolkit extension in VS Code and click “Preview your Teams app” in the “Development” section.
After logging in to your Teams account and adding your test application, you will see your Bryntum Gantt rendered. If you make changes to tasks and reload the page, the changes will persist.
You can also update multiple properties of a task by right-clicking on a taskbar, clicking “Edit”, and then changing multiple values. The updateOperation
function will also persist task reordering using drag-and-drop functionality.
Next steps
You now have a working version of the Gantt chart application in Microsoft Teams that you can use to visualize and manage project timelines.
So what comes next? Now that you’ve tested and verified the functionality of the application, you might like to deploy it to your production environment. Take a look at this Teams Toolkit documentation to find out how you can deploy your app to the cloud.
Are you already using Gantt chart inside Teams? If you’ve implemented Bryntum Gantt with MS Teams, we would be interested in hearing your feedback.