Tanay Karnik
1801 words, 9 minute read
Reading Open Source: Sentry (React)

I have been contemplating writing my first—actual—blog post for quite some time now, but there’s just something about writing a first blog post that makes me postpone the task endlessly. The struggle is undeniably real. I’ve ‘mentally’ written about a hundred posts—some drafted, others deleted—but not a single one published.

Today, I decided to finally suck it up and write it. So here goes.

It’s Sunday evening as I write this, and I’m sitting at a cafe. Hopefully, this change in environment will do the trick, and by the end of the day I will have published this post.


Anyway, back to the topic. A while ago, I wrote a post on X about how reading open-source code is an underused method to upskill. I used the Sentry codebase as an example, an excellent starting point for React.

How about we put it into action? Let’s dive into the Sentry codebase and see what lessons are in store.

The purpose of this exercise to learn as much as possible. It’s fine to miss details, or not understand everything fully. The goal is to explore the codebase, make smart observations, and take notes. If something seems complicated, we can always label it as a “black box” and come back to it later. Think of it as exploring a forest as opposed to climbing a mountain peak—we don’t have any specific destination.

I encourage you to follow along and make your notes; because learning by doing, remember?

I’m going to clone the repository because I like to tinker with the codebase and do random analysis (you’ll see). Alternatively, you can also use GitHub to read the codebase. GitHub has decent support for reading code with their definition/reference lookups. So, let’s get started.


First, I like to find out how big the codebase is.

Terminal window
git ls-files | wc -l
13270

That’s a hefty 13k files. Luckily, we are only interested in the React parts of it, so we can narrow it down. Sentry is a mono repo of both backend and frontend code with a mix of Python/Django, Rust, and TypeScript. Any guesses on where the React code is hidden?

It’s under a folder called static. The webpack.config.ts at the root of the project is configured to bundle from that folder, with static/index.ejs serving as the main HTML template (for HTMLWebpackPlugin). Also, if we ignore all the assets and “less” files, all the TypeScript code is under static/app. Let’s see the file count there.

Terminal window
cd static/app
git ls-files | wc -l
4532

A much more manageable number.

The folder structure#

We have a bunch of top-level directories and files. As an exercise, take a guess—just by looking at the name—what each file or directory might contain. For now, ignore the .spec.ts files; those are just unit tests and don’t “do anything” of their own.

Though looking at a spec file can be a good way to understand what the corresponding file or module is “supposed” to do.

Done guessing? Here’s what I guessed.

  • actionCreators - utils for creating redux actions
  • bootstrap - startup code, perhaps?
  • chartcuterie - A pun on “charts” and “charcuterie”, so something related to charts?
  • components - reusable React components
  • constants - Redux-related or other constants
  • data - text/json data
  • gettingStartedDocs - docs content
  • icons - icon files, duh
  • plugins - plugins, not sure for what
  • stores - redux stores
  • stories - no clue
  • styles - css styling
  • types - TypeScript types which get reused
  • utils - utility functions
  • views - React components not meant for reuse
  • api.tsx - setup code for calling backend APIs
  • index.tsx - entry point for the bundle
  • locale.tsx - translation/locale related setup
  • main.tsx - the main React component
  • routes.tsx - React router setup
  • sentryTypes.tsx - more TypeScript types?
  • utils.tsx - more utils or some kind of setup for utils

Now let’s dig into these one by one to quickly find out how well we guessed.

Why guess instead of just finding out what’s inside right away? It’s an exercise. The hope is that we can improve our guessing skills by doing this more often. Also, it creates a good excuse to purposefully look through the folders and files.

actionCreators#

Opening a random file here (actionCreators/dashboards.tsx), my guess was off. There’s no Redux here; these are just wrapper functions around the API Client (imported from static/app/api.tsx).

The general format of these functions is:

  • Take the inputs
  • Pass inputs to the API client correctly using the right method, query, body, and so on.
  • Handle any error responses.
  • Return a promise.

Sure enough, opening another bunch of files in the actionCreators folder, we see the same overall theme.

api.tsx#

Since the previous folder referenced this file frequently. It’s natural to look at this next.

And damn, this file seems to be import and handle a lot of things. It’s not as easy to parse as the files in the last folder, but we can handle it. Let’s scroll through, read symbol names and get an idea of what’s happening.

If you are on GitHub, just use the Symbols panel to the right and click on the symbols to hop to them.

I see names like: isSimilarOrigin, csrf, buildRequestUrl, indicating code for for making API calls, handling responses and errors. api.tsx seems to be the ideal place for it.

Most of the action in this file happens in the request function under the Client class, which can be summarized as:

  • Build the request url.
  • Package the body payload.
  • Do some “metrics” stuff using sentry/utils/analytics
  • Make the API call using fetch.
  • Handle success/errors and if something seems off, use Sentry (how meta) to capture exceptions for the devs to analyze.

This file gives us good insight into the general structure of most of Sentry’s backend APIs. There’s also a random hasProjectBeenRenamed function and I love it. It’s messy, but gets the job done.

In the Sentry Development Philosophy—a good read btw—they have a section called “Embrace The Duct Tape”. This function could be an example of such a duct tape solution.

stores#

Looking inside stores, we do find a bunch of store files, as expected. However, they aren’t Redux stores! Sentry uses Reflux and not Redux, so that was a bit presumptuous of me.

As I’m not really familiar with Reflux, I had to read through their GitHub docs to understand what’s happening. The concept is pretty similar: components call actions, actions mutate stores, and store updates trigger the relevant components to re-render.

For instance, open the alertStore.tsx.

The storeConfig defines the store, and also has some helper functions for mutating the store. The alertStore is really straightforward: the state is simply an array of alerts, and a count tracking the number of alerts.

Each alert has the type:

type Alert = {
message: React.ReactNode;
type: keyof Theme['alert'];
expireAfter?: number;
id?: string;
key?: number;
neverExpire?: boolean;
noDuplicates?: boolean;
onClose?: () => void;
opaque?: boolean;
url?: string;
};

The only actions are addAlert and closeAlert. addAlert updates the count and uses it to set the key of the new alert. closeAlert removes an alert from the store. Additionally, addAlert has some logic to automatically close an alert if expireAfter is used.

The store also has logic to handle “muted alerts” which uses local storage.

Overall, a cleanly written store that’s easy to understand. Heck, just reading the code paints a good picture of what’s happening, even for someone who has never used Sentry.

Other stores, like the projectsStore.tsx are more complex. The projects store utilizes the API client to refresh organization details when a new project is created, but the main structure is the same.

One thing that stands out for me, is the use of “side effects” like local storage and API calls. In Redux, having side effects inside reducers is discouraged, but Reflux allows actions to mutate the state and cause side effects. I like it.

styles#

So styles doesn’t house any CSS files. Instead, there are a few TypeScript files which use @emotion/react to export styles. Also, since there are so few style files, it’s reasonable to assume that that most of styling is written directly in the components or views files—or, remember the static/less folder we saw previously?

components and views#

components has a ton of React components! And all of them look like they get reused. Accordions, badges, banners, buttons, checkboxes, panels, and so on.

On the flip side, views has React components designed for single-use. These are thoughtfully named and organized, making it easy to imagine what role they would play in the Sentry app.

For instance, consider the app/alertMessage.tsx component, a view that gets used inside the app/systemAlerts.tsx view, and internally uses the Alert component from components/alert.

utils and utils.tsx#

The utils is folder is truly impressive. All complex logic that gets reused or can potentially get reused ends up in the utils folder.

If we check the number of lines in the whole React codebase:

Terminal window
find -type f | xargs wc -l | tail -1
247730 total

There are a total of 240k lines. Can you guess how much percentage of this code is inside the utils folder?

Terminal window
cd utils
find -type f | xargs wc -l | tail -1
120803 total

That’s right! About 50% of the code is inside the utils folder.

There are so many single function utils files, like utils/getDynamicText.tsx that has a single, small function which gets used in around 250 places. Each util file imports it’s own dependencies and maintaining lots of small files ensures there are no circular dependencies.

Some util files are REALLY big, like the discover/eventView.tsx file which has 1538 lines (though most of this code is inside the huge EventView class).

The utils.tsx file at the root is a small file with a collection of generic low-level utilities like arrayIsEqual and valueIsEqual—and some other random utils.

Digging deeper#

At this point, we have a decent understanding of various parts of the codebase, which means, we can dissect any flow in the Sentry app.

If you haven’t already, create a free account on Sentry, or use their sandbox environment: https://try.sentry-demo.com/

Try it out. Open the Sentry app and play around with it. Start with something simple—a basic UI component like a button or a badge and look through the code for it; then make it more complex gradually.

Identify flows that you find interesting—maybe something that’s similar to what you’ve worked on previously.

Rinse and repeat.

Keep guessing. Resist the urge to read the code rightaway. After you use a flow in the app, try to imagine how you would implement it, then look at the code to see how well you did.

Parting thoughts#

If you followed through the whole exercise, I encourage you to compile your list of insights gained from this exercise. Everyone has their takeaways and there’s no right or wrong—your takeaways could be very different than mine.

Also, remember that there’s no minimum skill requirement here. It’s an exercise, and you improve with repetition. Make observations, look up stuff, take notes, identify areas that you don’t understand, and either skip them or come back to them later.

There are so many good open-source codebases, you can repeat this exercise with more of them; there’s always a lot to learn.


Thanks for reading! If you liked reading this, let me know and I might do more such posts. Got some feedback? Feel free to reach out to me on X or via email.