# Patterns This contains the patterns that we follow in the Fleet UI. > NOTE: There are always exceptions to the rules, but we try as much as possible to follow these patterns unless a specific use case calls for something else. These should be discussed within the team and documented before merged. ## Table of contents - [Typing](#typing) - [Utilities](#utilities) - [Components](#components) - [Forms](#forms) - [Tier modes](#tier-modes) - [React hooks](#react-hooks) - [React Context](#react-context) - [Fleet API calls](#fleet-api-calls) - [Page routing](#page-routing) - [Command palette](#command-palette) - [Styles](#styles) - [Icons and images](#icons-and-images) - [Testing](#testing) - [Security considerations](#security-considerations) - [Other](#other) ## Typing All Javascript and React files use Typescript, meaning the extensions are `.ts` and `.tsx`. Here are the guidelines on how we type at Fleet: - Use *[global entity interfaces](../README.md#interfaces)* when interfaces are used multiple times across the app - Use *local interfaces* when typing entities limited to the specific page or component ### Local interfaces for page, widget, or component props ```typescript // page interface IPageProps { prop1: string; prop2: number; ... } // Note: Destructure props in page/component signature const PageOrComponent = ({ prop1, prop2 }: IPageProps) => { // ... }; ``` ### Local states with types ```typescript // Use type inference when possible. const [item, setItem] = useState(""); // Define the type in the useState generic when needed. const [user, setUser] = useState() ``` ### Fetch function signatures (i.e. `react-query`) ```typescript // include the types for the response, error. const { data } = useQuery( 'host', () => hostAPI.getHost() ) // include the third host data generic argument if the response data and exposed data are different. // This is usually the case when we use the `select` option in useQuery. // `data` here will be type IHostProfiles const { data } = useQuery( 'host', () => hostAPI.getHost() { // `data` here will be of type IHostResponse select: (data) => data.profiles } ) ``` ### Functions ```typescript // Type all function arguments. Use type inference for the return value type. // NOTE: sometimes typescript does not get the return argument correct, in which // case it is ok to define the return type explicitly. const functionWithTableName = (tableName: string)=> { // ... }; ``` ### API interfaces ```typescript // API interfaces should live in the relevant entities file. // Their names should clarify what they are used for when interacting with the // API. In service functions, prefer `formData` as the variable name for request // bodies to stay consistent with the *FormData interface naming convention. // should be defined in service/entities/hosts.ts interface IHostDetailsResponse { ... } interface IGetHostsQueryParams { ... } // should be defined in service/entities/users.ts interface IUpdateUserFormData { ... } // should be defined in service/entities/software.ts interface IGetSoftwareApiParams { ... } interface ISoftwareCountResponse { ... } // Use *FormData for form-driven bodies, *ApiParams/*QueryParams for request // params, *Response for responses, *QueryKey when typing a React Query key. // Avoid *Body, *PostBody, *Payload, *Request for API request bodies — use // *FormData instead, even for programmatic request bodies (e.g. // IDeleteQueriesFormData). One consistent suffix is easier to follow than // asking each dev to judge "is this form-driven enough?" // *PreviewPayload is fine for outgoing webhook shapes. ``` ## Utilities ### Named exports We export individual utility functions and avoid exporting default objects when exporting utilities. ```ts // good export const replaceNewLines = () => {...} // bad export default { replaceNewLines } ``` ### Software titles Software titles have two fields that look like a name: - `name` — the raw title from the installer/package metadata (e.g. `Microsoft.CompanyPortal`) - `display_name` — an optional custom name set per fleet by an admin Render the label from the resolved display name, but pass the **raw** `name` to `` — the icon matcher only knows raw, well-known names. #### Display name **Never render `name` directly in the UI.** Always route software names through `getDisplayedSoftwareName(name, display_name)` from `pages/SoftwarePage/helpers.tsx`. It prefers `display_name`, normalizes known awkward titles (e.g. `microsoft.companyportal` → `Company Portal`), and falls back to a sensible default. This applies everywhere a software title is shown: table rows, dropdown options, modal text, activity feed entries, automation summaries, etc. ```tsx // good label: getDisplayedSoftwareName(title.name, title.display_name), // bad — misses display_name and the WELL_KNOWN_SOFTWARE_TITLES normalization label: title.name, // also bad — misses the WELL_KNOWN_SOFTWARE_TITLES normalization label: title.display_name || title.name, ``` The same rule applies to any object shape that carries both fields (`ISoftwareTitle`, `ISoftwarePackage`, `IAppStoreApp`, `IHostSoftware`, `IPolicySoftwareToInstall`, etc.). The `ISoftwareTitle.name` JSDoc states the expectation: "All software names displayed by UI is ran through getDisplayedSoftwareName." #### Icons `` uses the `name` prop for **fallback icon matching** via `getMatchedSoftwareIcon({ name, source })` when `icon_url` is null. That matcher only knows the raw, well-known names (`notion`, `microsoft.companyportal`, etc.). If you pass it a resolved display name like `getDisplayedSoftwareName(...)` or `display_name || name`, an admin who renames the title to anything not in the match table will lose the icon to a generic fallback. Fleet-maintained apps are the highest-risk surface because they have no `icon_url` — they depend entirely on name matching. See #47123. When you have both fields, pass the raw `name` to the icon and the resolved name to the label: ```tsx // good — icon matches against raw name, label shows resolved display name const displayName = getDisplayedSoftwareName(title.name, title.display_name); <> {displayName} // bad — admin renames break the icon match for FMAs and other matched titles ``` When the data has been flattened into a single `name` field upstream (e.g. for a row object or table-cell renderer), carry the raw name alongside it as a separate field (`iconName`, `rawName`, etc.) and feed THAT to ``. See `frontend/pages/policies/ManagePoliciesPage/helpers.tsx`'s `ISoftwareAutomationData.iconName` for the established pattern. `SoftwareNameCell` already does this internally — when you can use it, prefer it over hand-rolling icon + label rendering. ## Components ### React functional components We use functional components with React instead of class comonents. We do this as this allows us to use hooks to better share common logic between components. ### Passing props into components We strongly prefer explicit assignment of prop values over object spread syntax. In almost all cases, list every prop by name: ```tsx ``` Spreading is hard to review (the reader can't see what's being passed), brittle under refactors (adding a key to the source bag silently changes the target), and on native DOM elements it's a real security footgun — anything in the bag (including `dangerouslySetInnerHTML`, `href`, `src`, event handlers) gets applied. #### Accepted exceptions Spread is acceptable in these cases: - **react-select 5 custom subcomponents** — the library contract requires forwarding the full internal props bag (`innerRef`, `innerProps`, `selectProps`, …) to `components.X`. The bag is library-generated, not user input. - **react-table v7 prop getters** (`getCellProps()`, `getRowProps()`, `getHeaderProps()`, `getToggleAllRowsSelectedProps()`) — the prop-getter pattern *is* the library's API. Cell data is rendered through `cell.render("Cell")`, never as attributes. - **react-markdown renderer overrides** (e.g. `code: ({...props}) => `) — the bag is library-controlled HAST metadata, not raw markdown. Do not enable the `rehype-raw` plugin: it lets raw HTML from the markdown source pass through to the bag, and the spread would forward it straight to the DOM — author-controlled HTML rendering verbatim is an XSS sink. - **Typed SVG icon components** (`SVGProps` flowing into ``, as in `pages/SoftwarePage/components/icons/*`) — the `SVGProps` type constrains callers to valid SVG attributes. Do *not* widen the prop type to `any` or `Record`; that removes the guard that makes this safe. - **Test helpers, factories, and Storybook stories** — non-production code. The bag is built locally in the same file by code that owns its shape. #### Not safe — never spread Never spread props (especially anything derived from API responses, URLs, markdown source, MDM payloads, host facts, software metadata, or other external data) onto: ``, ``, `