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
- use
index.(j|t)? (sx)
- React
- define
React
component - Components and Views
- PropTypes
- Event handler
- define
- CSS and Emotion
stylelint
error
- State management
- test
- The selector
- Undefined in the test
theme
attribute
- 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
- Class (
- The use of Hooks
- Using the library
hooks
- use
react
The built-inhooks
- use
context
- Using custom
hooks
- Pay attention to
hooks
Rules and precautions - Our base view components are still class-based
- Don’t be
hooks
rewrite
- Using the library
- Use the React Testing Library
- The query
- skills
- Migration – the grid – emotion
- component
- attribute
margin
和padding
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.e
dataScrubbingEditModal
,dataScrubbingAddModal
), but using images likedataScrubbing/editModal
That’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
- use
Emotion
, the use oftheme
Object. - The best styles are the ones you don’t write – use existing components wherever possible.
- New code should be used
css-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.Flex
和Box
Deprecated, avoid using it in new code.
stylelint
error
“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 testtheme
attribute
Instead of using mount() from enzyme… Use this: import {mountWithTheme} from ‘sentry-test/enzyme’ to use
- 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 possible
getBy...
- Use only when the check does not exist
queryBy...
- Only when expected elements in May not happen immediately
DOM
Use 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:
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/…
getByLabelText
/getByPlaceholderText
– User usagelabel
Text finds form elements, so this option is preferred when testing forms.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
,span
和paragraph
).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
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