Content is compiled from official development documentation

A series of

  • 1 minute Quick use of The latest version of Docker Sentry-CLI – create version
  • Quick use of Docker start Sentry-CLI – 30 seconds start Source Maps
  • Sentry For React
  • Sentry For Vue
  • Sentry-CLI usage details
  • Sentry Web Performance Monitoring – Web Vitals
  • Sentry Web performance monitoring – Metrics
  • Sentry Web Performance Monitoring – Trends
  • Sentry Web Front-end Monitoring – Best Practices (Official Tutorial)
  • Sentry Backend Monitoring – Best Practices (Official Tutorial)
  • Sentry Monitor – Discover Big data query analysis engine
  • Sentry Monitoring – Dashboards Large screen for visualizing data
  • Sentry Monitor – Environments Distinguishes event data from different deployment Environments
  • Sentry monitoring – Security Policy Reports Security policies
  • Sentry monitoring – Search Indicates the actual Search
  • Sentry monitoring – Alerts Indicates an alarm
  • Sentry Monitor – Distributed Tracing
  • Sentry Monitoring – Distributed Tracing 101 for Full-stack Developers
  • Sentry Monitoring – Snuba Data Mid platform Architecture introduction (Kafka+Clickhouse)
  • Sentry – Snuba Data Model
  • Sentry Monitoring – Snuba Data Mid-Platform Architecture (Introduction to Query Processing)
  • Sentry official JavaScript SDK introduction and debugging guide
  • Sentry Monitoring – Snuba Data Mid-platform architecture (writing and testing Snuba queries)
  • Sentry Monitoring – Snuba Data Medium Architecture (SnQL Query Language introduction)
  • Sentry Monitoring – Snuba Data Mid – platform local development environment configuration combat
  • Sentry Monitor – Private Docker Compose deployment and troubleshooting

directory

  • The front-end manual
    • The directory structure
    • Folder and file structure
      • The file name
      • useindex.(j|t)? (sx)
    • React
      • defineReactcomponent
      • Components and Views
      • PropTypes
      • Event handler
    • CSS and Emotion
      • stylelinterror
    • State management
    • test
      • The selector
      • Undefined in the testthemeattribute
    • Babel syntax plug-in
    • New syntax
      • Optional chain
      • grammar
      • A null value merge
    • Lodash
    • Typescript
    • Migration guide
  • Storybook Styleguide
    • Do we use it?
    • Is it deployed somewhere?
  • Typing DefaultProps
    • Class (Class) components
    • Function (Function) components
    • reference
  • The use of Hooks
    • Using the libraryhooks
    • usereactThe built-inhooks
    • usecontext
    • Using customhooks
    • Pay attention tohooksRules and precautions
    • Our base view components are still class-based
    • Don’t behooksrewrite
  • Use the React Testing Library
    • The query
    • skills
  • Migration – the grid – emotion
    • component
    • attribute
      • marginpadding
      • flexbox

The front-end manual

This guide covers how we write front-end code in Sentry, with a special focus on the Sentry and Getsentry codebase. It assumes that you are using the ESLint rule outlined in eslint-config-sentry; Therefore, the code styles enforced by these Linting rules will not be discussed here.

  • Github.com/getsentry/e…

The directory structure

Front-end code libraries currently located in the SRC/sentry sentry/static/sentry/app and under the static/getsentry getentry. (We plan to be consistent with Static/Sentry in the future.)

Folder and file structure

The file name

  • Meaningfully name files based on the functionality of modules or how classes are used or the parts of the application that use them.
  • Do not use prefixes or suffixes unless necessary (i.edataScrubbingEditModal,dataScrubbingAddModal), but using images likedataScrubbing/editModalThat’s the name.

useindex.(j|t)? (sx)

Having an index file in the folder provides a way to implicitly import the main file without specifying it

Use of index files should follow the following rules:

  • If you create folders to group components used together and have an entry point component, it uses components within the group (examples, Avatar, idBadge). The entry point component should be an index file.

  • Don’t use index. (j | t)? (SX) files, if folders contain components used in other parts of the application, are not related to entry point files. (That is, actionCreators, panels)

  • Do not use index files just for re-export. Prefer to import individual components.

React

Defining the React component

The new component uses the class syntax when it needs to access this, along with the class field + arrow function method definition.

class Note extends React.Component {
  static propTypes = {
    author: PropTypes.object.isRequired,
    onEdit: PropTypes.func.isRequired,
  };

  // Notice that the method is defined using the arrow function class field (bind "this")
  handleChange = value= > {
    let user = ConfigStore.get('user');

    if (user.isSuperuser) {
      this.props.onEdit(value); }};render() {
    let {content} = this.props; // Use destruct assignment for props

    return <div onChange={this.handleChange}>{content}</div>; }}export default Note;
Copy the code

Some older components use createReactClass and mixins, but this has been deprecated.

Components and Views

The app/ Components/and app/ Views folders both contain React components.

  • Use UI views that are not normally reused in other parts of the code base.
  • Use UI components that are designed to be highly reusable.

The component should have an associated.stories.js file to document how it should be used.

Using yarn storybook running locally storybook or view the hosted version on https://storybook.getsentry.net/

PropTypes

To use them, be sure to use shared custom attributes whenever possible.

Favoring Proptypes. ArrayOf over Proptypes. Array and Proptypes. Shape over Proptypes

If you pass objects with a set of important, well-defined keys (your component dependencies), define them explicitly using proptypes.shape:

PropTypes.shape({
  username: PropTypes.string.isRequired,
  email: PropTypes.string
})
Copy the code

If you’re going to reuse custom prop-Types or pass common shared shapes like Organization, Project, or User, make sure to import the PropType from our useful custom collection!

  • Github.com/getsentry/s…

Event handler

We use different prefixes to better distinguish the event handler from the event callback properties.

Use the handle prefix for event handlers, for example:

<Button onClick={this.handleDelete}/>
Copy the code

For event callback properties passed to the component, use the on prefix, for example:

<Button onClick={this.props.onDelete}>
Copy the code

CSS and Emotion

  • useEmotion, the use ofthemeObject.
  • The best styles are the ones you don’t write – use existing components wherever possible.
  • New code should be usedcss-in-js 库 e m o t i o n- It allows you to bind styles to elements without the indirection of a global selector. You don’t even have to open another file!
  • fromprops.themeGet constants (z-indexes.paddings.colors)
    • emotion.sh/
    • Github.com/getsentry/s…
import styled from 'react-emotion';

const SomeComponent = styled('div')` border - the radius: 1.45 em. font-weight: bold; z-index:${p => p.theme.zIndex.modal};
  padding: ${p => p.theme.grid}px ${p => p.theme.grid * 2}px;
  border: 1px solid ${p => p.theme.borderLight};
  color: ${p => p.theme.purple};
  box-shadow: ${p => p.theme.dropShadowHeavy};
`;

export default SomeComponent;
Copy the code
  • Please note that,reflexbox(e.g.FlexBoxDeprecated, avoid using it in new code.

stylelinterror

“No duplicate selectors”

This happens when you use the Style Component as a selector, and we need to inform the Stylelint that we are inserting a selector by assisting the Linter with annotations. For example,


const ButtonBar = styled("div")`
  The ${/* sc-selector */Button) {
     border-radius: 0;
  }
`;
Copy the code

For additional labels and more information, see.

  • Styled-components.com/docs/toolin…

State management

We currently use Reflux to manage global state.

The Reflux implements a one-way data flow pattern outlined by Flux. Store is registered under app/ Stores and is used to Store various data used by applications. Actions need to be registered under app/ Actions. We used the Action Creator function (under app/actionCreators) to dispatch the action. The Reflux Store listens on actions and updates itself accordingly.

We are currently exploring alternatives to the Reflux library for future use.

  • Github.com/reflux/refl…
  • Facebook. Making. IO/flux/docs/o…

test

Note: Your file name must be.spec. JSX or Jest won’t run it!

We defined the useful fixtures in setup.js, use these! If you are defining mock data in a repetitive way, it may be worth adding this file. RouterContext is a particularly useful way to provide context objects on which most views depend.

  • Github.com/getsentry/s…

Client.addmockresponse is the best way to simulate an API request. This is our code, so if it confuses you, just put console.log() statements into its logic!

  • Github.com/getsentry/s…

An important issue in our test environment was that the Enzyme modified many aspects of the React lifecycle to be evaluated synchronously (even though they were usually asynchronous). This can lull you into a false sense of security when you trigger certain logic that is not immediately reflected in your assertion logic.

Mark your test method async and use await tick(); The utility can tell the event loop to refresh run events and fix this problem:

wrapper.find('ExpandButton').simulate('click');
await tick();
expect(wrapper.find('CommitRow')).toHaveLength(2);
Copy the code

The selector

If you are writing JEST tests, you can use the Component (and Styled Component) name as a selector. Also, if you need to use a DOM query selector, use data-test-id instead of the class name. We don’t currently have it, but we can use Babel to remove it during the build process.

Undefined in the testthemeattribute

Instead of using mount() from enzyme… Use this: import {mountWithTheme} from ‘sentry-test/enzyme’ to use for the component under test.

  • Emotion. Sh/docs/themin…

Babel syntax plug-in

We decided to use only the ECMAScript proposal in Stage 3 (or later) (see TC39 proposal). Also, because we are migrating to typescript, we will be aligned with what their compiler supports. The only exception is decorators.

  • Github.com/tc39/propos…

New syntax

Optional chain

Optional chains help us access [nested] objects without having to check for existence before each property/method is accessed. If we try to access the attribute of undefined or null object, it will stop and return undefined.

  • Github.com/tc39/propos…
grammar

The optional chain operator is spelled? . It may appear in three locations:

obj? .prop // Optional static property access to obj? .[expr] // Optional dynamic attribute access func? . (... Args) // Optional function or method callCopy the code

From github.com/tc39/propos…

A null value merge

This is one way to set a “default” value. You used to do things like this

let x = volume || 0.5;
Copy the code

This is a problem because 0 is a valid value for volume, but because it evaluates to false -y, we don’t short-circuit the expression, and x has a value of 0.5

If we use null value merge

  • Github.com/tc39/propos…
let x = volume ?? 0.5
Copy the code

If volume is null or undefined, it only defaults to 0.5.

grammar

Basic information. If the expression is in?? Returns the right-hand side of the operator that evaluates to undefined or null.

const response = {
  settings: {
    nullValue: null.height: 400.animationDuration: 0.headerText: ' '.showSplashScreen: false}};const undefinedValue = response.settings.undefinedValue ?? 'some other default'; // result: 'some other default'
const nullValue = response.settings.nullValue ?? 'some other default'; // result: 'some other default'
const headerText = response.settings.headerText ?? 'Hello, world! '; // result: ''
const animationDuration = response.settings.animationDuration ?? 300; // result: 0
const showSplashScreen = response.settings.showSplashScreen ?? true; // result: false
Copy the code

The From github.com/tc39/propos…

Lodash

Be sure not to import the LoDash utility using the default LoDash package. There is an ESLint rule to make sure this doesn’t happen. Instead, import utilities directly, such as import isEqual from ‘lodash/isEqual’; .

Previously we used a combination of LoDash-webpack-plugin and babel-plugin-LoDash, but it’s easy to ignore these plug-ins and configurations when trying to use new LoDash utilities such as this PR. With WebPack Tree shaking and ESLint enforcement, we should be able to keep package sizes reasonable.

  • www.npmjs.com/package/lod…
  • Github.com/lodash/babe…
  • Github.com/getsentry/s…

See this PR for more information.

  • Github.com/getsentry/s…

We prefer to use optional chains and null value merges rather than get from Lodash /get.

Typescript

  • Typing DefaultProps

Migration guide

  • Grid-Emotion

Storybook Styleguide

To quote its documentation, “Storybook is a UI development environment for UI components. With it, you can visualize the different states of UI components and develop them interactively.”

More details here:

  • storybook.js.org/

Do we use it?

Yes! We use Storybook for the getSentry/Sentry project. Storybook configuration is available at github.com/getsentry/s… Found.

To run Storybook locally, run NPM Run Storybook in the root of the getSentry/Sentry repository.

Is it deployed somewhere?

Sentry’s Storybook is built and deployed using Vercel. Each Pull Request has its own deployment, and each push to the main branch is deployed to storybook.sentry.dev.

  • storybook.sentry.dev

Typing DefaultProps

Because Typescript 3.0 default props can be typed more easily. There are several different approaches suitable for different scenarios.

Component classes (Class)

import React from 'react';

type DefaultProps = {
  size: 'Small' | 'Medium' | 'Large'; // These should not be marked optional
};

/ / not Partial < DefaultProps >
type Props = DefaultProps & {
  name: string; codename? : string; };class Planet extends React.Component<Props> {
  // No Partial
      
        because it marks everything as optional
      
  static defaultProps: DefaultProps = {
    size: 'Medium'};render() {
    const {name, size, codename} = this.props;

    return (
      <p>
        {name} is a {size.toLowerCase()} planet.
        {codename && ` Its codename is ${codename}`}
      </p>); }}const planet = <Planet name="Mars" />;
Copy the code

Or with typeof’s help:

import React from 'react';

const defaultProps = {
  size: 'Medium' as 'Small' | 'Medium' | 'Large'}; type Props = {name: string; codename? : string; } &typeof defaultProps;
// There is no Partial
      
        because it marks everything as optional
      

class Planet extends React.Component<Props> {
  static defaultProps = defaultProps;

  render() {
    const {name, size, codename} = this.props;

    return (
      <p>
        {name} is a {size.toLowerCase()} planet. Its color is{' '}
        {codename && ` Its codename is ${codename}`}
      </p>); }}const planet = <Planet name="Mars" />;
Copy the code

Functional components

import React from 'react';

// defaultProps on function components will be stopped in the future
// https://twitter.com/dan_abramov/status/1133878326358171650
// https://github.com/reactjs/rfcs/pull/107
// We should use the default parameters

type Props = {
  name: string; size? :'Small' | 'Medium' | 'Large'; // Properties with es6 default parameters should be marked as optionalcodename? : string; };// The consensus is that input deconstructed Props is slightly better than using React.FC
      
// https://github.com/typescript-cheatsheets/react-typescript-cheatsheet#function-components
const Planet = ({name, size = 'Medium', codename}: Props) = > {
  return (
    <p>
      {name} is a {size.toLowerCase()} planet.
      {codename && ` Its codename is ${codename}`}
    </p>
  );
};

const planet = <Planet name="Mars" />;
Copy the code

reference

  • The Typescript 3.0 Release notes
    • www.typescriptlang.org/docs/handbo…
  • Stack Overflow question on typing default props
    • Stackoverflow.com/questions/3…

The use of Hooks

To make components easier to reuse and understand, the React and React ecosystems have always gravitate toward functional components and hooks. Hooks are a convenient way to add state and side effects to functional components. They also provide a convenient way for libraries to expose behavior.

While we generally support hooks, we have some suggestions for how hooks should be used with the Sentry front end.

Use hooks from the library

If a library provides hooks, you should use them. Often, this will be the only way to use the library. For example, DND-Kit exposes all its primitives via hooks, and we should use the library as expected.

We don’t like using libraries that don’t use hooks. Instead, libraries with cleaner, simpler apis and smaller package sizes are preferred over libraries with larger, more complex apis or larger package sizes.

Use the built-in hooks for React

UseState, useMemo, useCallback, useContext, and useRef hooks are welcome in any functional component. They are often a good choice for presentation components that require a small amount of state or access react primitives such as references and contexts. For example, a component that has slide-out or expandable state.

UseEffect Hook is more complex, and you need to carefully track your dependencies and make sure to unsubscribe by cleaning up callbacks. Complex chained applications with useEffect should be avoided, while the ‘controller’ component should remain class-based.

Also, the useReducer hook overlaps with state management, which is as yet unidentified. We want to avoid another state management mode, so avoid using useReducer at this time.

Use the context

UseContext Hook provides a simpler implementation option to share state and behavior when we plan to move away from the Reflux path. When you need to create new shared state sources, consider using Context and useContext instead of Reflux. In addition, the wormhole state management pattern can be used to expose shared state and mutation functions.

  • Swizec.com/blog/wormho…

Use custom hooks

You can create custom hooks to share reusable logic in your application. When you create custom hooks, the function name must follow the convention, beginning with “use” (for example, useTheme), and you can call other hooks within custom hooks.

Pay attention to hooks rules and cautions

React hooks have some rules. Note the rules and restrictions created by hooks. We use the ESLint rule to prevent most hook rules from being hacked.

  • Reactjs.org/docs/hooks-…

In addition, we recommend that you use useEffect as little as possible. Using multiple useEffect callbacks indicates that you have a highly stateful component that you should use a class component instead.

Our base view components are still class-based

Our base view components (AsyncView and AsyncComponent) are class-based and last a long time. Keep this in mind when building your views. You will need additional Wrapper components to access hooks or to convert hook states to props for your AsyncComponent.

Do not rewrite for hooks

While hooks can be ergonomic in new code, we should avoid rewriting existing code to take advantage of hooks. Rewriting takes time, puts us at risk, and provides little value to the end user.

If you need to redesign a component to use hooks in the library, you can also consider converting from a class to a function component.

Use the React Testing Library

We are converting our tests from the Enzyme to the React Testing Library. In this guide, you’ll find tips for following best practices and avoiding common pitfalls.

We have two ESLint rules to help solve this problem:

  • eslint-plugin-jest-dom
    • Github.com/testing-lib…
  • eslint-plugin-testing-library
    • Github.com/testing-lib…

We try to write tests in a way that is very similar to how applications are used.

Instead of dealing with instances of rendered components, we query the DOM in the same way as the user. We find form elements through the label text (just like the user), and we find links and buttons from their text (just like the user).

As part of this goal, we avoid testing implementation details, so refactoring (changing the implementation but not the functionality) does not break the tests.

We generally favor use case coverage over code coverage.

The query

  • Use whenever possiblegetBy...
  • Use only when the check does not existqueryBy...
  • Only when expected elements in May not happen immediatelyDOMUse only when the change appearsawait findBy...

To ensure that the tests are similar to how users interact with our code, we recommend using the following priorities for queries:

  1. getByRole– This should be the preferred selector for almost everything.

As a nice bonus for this selector, we made sure our application was accessible. It is likely to be used with the name option getByRole(‘button’, {name: /save/ I}). Name is typically the label of a form element or the text content of a button, or the value of an aria-label attribute. If in doubt, use the logRoles feature or consult the list of available roles.

  • Testing-library.com/docs/dom-te…
  • Developer.mozilla.org/en-US/docs/…
  1. getByLabelText/getByPlaceholderText– User usagelabelText finds form elements, so this option is preferred when testing forms.
  2. getByText– Outside of forms, text content is the primary way users find elements. This method can be used to find non-interactive elements such asdiv,spanparagraph).
  3. getByTestId– Because this does not reflect how the user interacts with the application, it is recommended only when you cannot use any other selectors

If you still can’t decide which one to use the query, see testing-playground.com and screen. LogTestingPlaygroundURL () and its browser extensions.

  • testing-playground.com/

Don’t forget that you can place screen.debug() anywhere in your test to view the current DOM.

Read more about queries in the official documentation.

  • Testing-library.com/docs/querie…

skills

Avoid deconstructing query functions from the Render method and use Screen (examples) instead. You don’t have to keep the Render call deconstruction up to date when you add/remove the queries you need. You just type Screen and let your editor’s autocomplete take care of the rest.

  • Github.com/getsentry/s…
import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary";

/ / ❌
const { getByRole } = mountWithTheme(<Example />);
const errorMessageNode = getByRole("alert");

/ / ✅
mountWithTheme(<Example />);
const errorMessageNode = screen.getByRole("alert");
Copy the code

In addition to checking for examples that do not exist, avoid queryBy… Used for anything. If no element is found, getBy… And findBy… Variable will throw a more useful error message.

  • Github.com/getsentry/s…
import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary";

/ / ❌
mountWithTheme(<Example />);
expect(screen.queryByRole("alert")).toBeInTheDocument();

/ / ✅
mountWithTheme(<Example />);
expect(screen.getByRole("alert")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
Copy the code

Instead of using waitFor to waitFor occurrences, use findBy… (examples). These two are basically equivalent (findBy… It even uses waitFor), but findBy… It’s easier and we get better error messages.

  • Github.com/getsentry/s…
import {
  mountWithTheme,
  screen,
  waitFor,
} from "sentry-test/reactTestingLibrary";

/ / ❌
mountWithTheme(<Example />);
await waitFor(() = > {
  expect(screen.getByRole("alert")).toBeInTheDocument();
});

/ / ✅
mountWithTheme(<Example />);
expect(await screen.findByRole("alert")).toBeInTheDocument();
Copy the code

Avoid using waitForElementToBeRemoved use the waitFor wait disappear, instead of (examples).

  • Github.com/getsentry/s…

The latter uses a MutationObserver, which is more efficient than polling the DOM regularly with waitFor.

import {
  mountWithTheme,
  screen,
  waitFor,
  waitForElementToBeRemoved,
} from "sentry-test/reactTestingLibrary";

/ / ❌
mountWithTheme(<Example />);
await waitFor(() = >
  expect(screen.queryByRole("alert")).not.toBeInTheDocument()
);

/ / ✅
mountWithTheme(<Example />);
await waitForElementToBeRemoved(() = > screen.getByRole("alert"));
Copy the code

Jest-dom assertions (examples) are preferred. The advantages of using these recommended assertions are better error messages, overall semantics, consistency, and uniformity.

  • Github.com/getsentry/s…
import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary";

/ / ❌
mountWithTheme(<Example />);
expect(screen.getByRole("alert")).toBeTruthy();
expect(screen.getByRole("alert").textContent).toEqual("abc");
expect(screen.queryByRole("button")).toBeFalsy();
expect(screen.queryByRole("button")).toBeNull();

/ / ✅
mountWithTheme(<Example />);
expect(screen.getByRole("alert")).toBeInTheDocument();
expect(screen.getByRole("alert")).toHaveTextContent("abc");
expect(screen.queryByRole("button")).not.toBeInTheDocument();
Copy the code

When searching by text, it is best to use a case-insensitive regular expression. It will make the test more adaptable to change.

import { mountWithTheme, screen } from "sentry-test/reactTestingLibrary";

/ / ❌
mountWithTheme(<Example />);
expect(screen.getByText("Hello World")).toBeInTheDocument();

/ / ✅
mountWithTheme(<Example />);
expect(screen.getByText(/hello world/i)).toBeInTheDocument();
Copy the code

Use userEvent on fireEvent whenever possible. UserEvent comes from the @testing-library/ user-Event package, which is built on top of fireEvent, but provides several methods that are more similar to user interaction.

/ / ❌
import {
  mountWithTheme,
  screen,
  fireEvent,
} from "sentry-test/reactTestingLibrary";
mountWithTheme(<Example />);
fireEvent.change(screen.getByLabelText("Search by name"), {
  target: { value: "sentry"}});/ / ✅
import {
  mountWithTheme,
  screen,
  userEvent,
} from "sentry-test/reactTestingLibrary";
mountWithTheme(<Example />);
userEvent.type(screen.getByLabelText("Search by name"), "sentry");
Copy the code

Migration – the grid – emotion

Grid-emotion has been deprecated for over a year and the new project is the ReflexBox. To upgrade to the latest version of Emotion, we need to migrate out of Grid-Emotion.

To migrate, use emotion to replace the imported

and

components with styled components.

component

Replace the component with the following, then remove the necessary props and move to the Styled Component.

<Flex>

const Flex = styled('div')`
  display: flex;
`;
Copy the code

<Box>

const Box = styled('div')`
`;
Copy the code

props

If you are modifying an exported component, be sure to grep through the component’s code base to make sure it is not rendered as an additional property grid-emotion specific. The example is a component.

Margin and padding

The Margin property starts with m and is filled with P. The following example will use margin as an example

Old (grid – emotion) New (CSS/emotion/styled)
m={2} margin: ${space(2);
mx={2} margin-left: ${space(2); margin-right: ${space(2)};
my={2} margin-top: ${space(2); margin-bottom: ${space(2)};
ml={2} margin-left: ${space(2);
mr={2} margin-right: ${space(2);
mt={2} margin-top: ${space(2);
mb={2} margin-bottom: ${space(2);

flexbox

These are flexbox properties

Old (grid – emotion) New (CSS/emotion/styled)
align="center" align-items: center;
justify="center" justify-content: center;
direction="column" flex-direction: column;
wrap="wrap" flex-wrap: wrap;

For now just ignore grid-emotion import statements, such as // eslint-disable-line no-restricted-imports