Formulas, Database

How to Track Project Progress Without Rollups

Track sub-item progress

Notion formulas 2.0 offer a new way to track project progress without using rollups using the new map() function. This can be useful for complex projects with nested tasks, or for projects where you need more control over how progress is calculated.

Difficulty: this tutorial is ideal for novice Notion users who are somewhat familiar with how to setup databases, implement simple formulas, and structure data.

Prepare the Projects Database

To create a new database table, type “/table” into any empty page.

To create a database to track project progress without rollups, you will need the following properties:

  • Name (title): Name of the project or task.

  • Parent item (relation): Name of the parent project a sub-item is related to.

    • Sub-item (relation): Name of the sub-item(s) a parent item is related to.

  • Done (checkbox, optional): Check true if the project or task is complete.

  • Status (select, optional): Choose between 3 options:

    • Not started

    • In progress

    • Completed

  • Progress (formula): A formula property to track sub-item progress.

The Sub-items property will allow you to link related tasks to each project. The Done and Status properties are optional, but they can be useful for tracking the progress of each task.

 

Setup Sub-items for Projects

Sub-items are a powerful tool for managing projects in Notion. They allow you to create a hierarchy of tasks, with each parent task, or project, having its own sub-tasks. This can be very helpful for breaking down large projects into more manageable chunks.

For example, here are sub-items for the project Launch new website:

  • Launch new website (project)

    • Design website

    • Develop website

    • Write content

    • Test website

    • Launch website

To create sub-tasks, locate the database menu and toggle on Sub-items.

This will provide two new properties (parent and sub-items) upon activating. In a table database, you’ll notice a gray toggle next to each row. Expand the toggle to add new sub-items.

 

Build the Progress Bar Formula

Once you have created a database with sub-items, you can use formulas to calculate the overall progress of your project.

Here is a formula to calculate project progress using a checkbox property:

if(!prop("Sub-item").empty(), 
	floor(100 *
		prop("Sub-item").map(
			current.prop("Done").toNumber()
		).sum() / 
		prop("Sub-item").length()
	) / 100,
	prop("Done").toNumber()
)

Formula Breakdown

  • if(!prop("Sub-item").empty(),

    This line checks if the Sub-item property is empty. If it is not empty, the formula proceeds to calculate the percentage of sub-items that are done. If it is empty, see the last line of code.

  • floor(100 *
    	prop("Sub-item").map(
    		current.prop("Done").toNumber()
    	).sum() /
    	prop("Sub-item").length()
    ) / 100

    This section of the formula calculates the percentage of sub-items that are done. It does this by:

  • prop("Sub-item").map(
    	current.prop("Done").toNumber()

    Mapping the Done property of all current Sub-items to a number. Using the toNumber() function on a checkbox converts true to 1 and false to 0.

    The map() function returns all values from a related pages, like a rollup property, using the code: property.map(current).

    Rollup a specified property by stringing the property name after current: property.map(current.property).

  • ).sum() /
    	prop("Sub-item").length()

    Summing the numbers from the previous step with the sum() function.

    Dividing the sum by the number of total Sub-items with the length() function.

  • floor(100 * {…}) / 100

    Flooring the result to round it down to the nearest integer with the floor() function. To round a number down to the nearest hundredth integer, use this code: floor(100 * number) / 100.

  • prop("Done").toNumber())

    This line returns the value of the Done property as a number if the Sub-items property is empty.

 

Here is a formula to calculate project progress using a status property:

if(!prop("Sub-item").empty(),
	floor(100*
		prop("Sub-item").filter(
			current.prop("Status") == "Completed"
		).length() /
		prop("Sub-item").length()
	) / 100,
	replace(
		prop("Status"), "Completed", "1"
	).toNumber()
)

Formula Breakdown

  • if(!prop("Sub-item").empty(),

    This line checks if the Sub-item property is empty. If it is not empty, the formula proceeds to calculate the percentage of sub-items that are done. If it is empty, see the last line of code.

  • floor(100*
    	prop("Sub-item").filter(
    		current.prop("Status") == "Completed"
    	).length() /
    		prop("Sub-item").length()
    ) / 100,

    This section of the formula calculates the percentage of sub-items that are done. It does this by:

  • prop("Sub-item").filter(
    	current.prop("Status") == "Completed"

    Filtering the Status property of all current Sub-items with the status “Completed”.

  • ).length() /
    	prop("Sub-item").length()

    Returning the length (total pages) of all Sub-items with a “Completed” status from the previous step.

    Dividing the number of filtered items by the number of total Sub-items with the length() function.

  • floor(100 * {…}) / 100

    Flooring the result to round it down to the nearest integer with the floor() function. To round a number down to the nearest hundredth integer, use this code: floor(100 * number) / 100.

  • replace(
    	prop("Status"), "Completed", "1"
    ).toNumber())

    This line returns the value of 1 if the Status property is “Completed” and the Sub-items property is empty.

    To achieve this, use the replace() function to replace any “Completed” string with “1.” Then, convert this string into a number using toNumber().

 

Convert a Number into a Bar

These formulas will result in a number between 0-1. Convert this number into a percent in the property’s menu (click on the property title + edit property). An option to view the number as a bar or ring will also be available in this menu:

 

Map and Filter Functions Explained

When to use map():

The map function returns all related pages or associated values.

  • Return all related pages as a list.

  • Return a list of values associated with related pages.

  • Perform calculations on values associated with related pages.

map() example:

This formula will return a new list containing the Status of each task in the Tasks property as a list.

prop("Tasks").map(current.prop("Status"))

When to use filter():

The filter function returns some related pages or associated values based on specified criteria.

  • Return all related pages as a list that meet a criteria.

  • Return a list of values associated with related pages that meet a criteria, and perform calculations on the filtered list.

filter() example:

This formula will return a new list containing only the tasks in the Tasks property that have a Status of "Done".

prop("Tasks").filter(current.prop("Status") == "Done")