diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 146bea26c61..4da3dbe498b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -47,7 +47,9 @@ jobs: path: ref_code_coverage.txt current_branch: - if: github.event.pull_request.draft == false + # We want to make sure we only run on open PRs (not drafts), but also should run even if the base branch coverage job fails. + # If the base branch coverage job fails to create a report, the current branch coverage job will fail as well, but this may help us debug the CI on the current branch. + if: ${{ always() && github.event.pull_request.draft == false }} runs-on: ubuntu-latest needs: base_branch diff --git a/docs/tooling/analytics.md b/docs/tooling/analytics.md index 7ba441792d9..a0ab6b9e474 100644 --- a/docs/tooling/analytics.md +++ b/docs/tooling/analytics.md @@ -1,12 +1,15 @@ -# Adobe Analytics +# Analytics + +## Adobe Analytics Cloud Manager uses Adobe Analytics to capture page view and custom event statistics. To view analytics, Cloud Manager developers must follow internal processes to request access to Adobe Analytics dashboards. -## Writing a Custom Event +### Writing a Custom Event Custom events live (mostly) in `src/utilities/analytics/customEventAnalytics.ts`. Try to write and export custom events in this file if possible, and import them in the component(s) where they are used. A custom event will take this shape: + ```tsx // Component.tsx {file(s) where the event is called, for quick reference} // OtherComponent.tsx @@ -33,7 +36,7 @@ Examples - `sendMarketplaceSearchEvent` fires when selecting a category from the dropdown (`label` is predefined) and clicking the search field (a generic `label` is used). - `sendBucketCreateEvent` sends the region of the bucket, but does not send the bucket label. -## Writing Form Events +### Writing Form Events Form events differ from custom events because they track user's journey through a flow and, optionally, branching flows. Form events live in `src/utilities/analytics/formEventAnalytics.ts`. Try to write and export custom events in this file if possible, and import them in the component(s) where they are used. @@ -53,10 +56,34 @@ These are the form events we use: See the `LinodeCreateForm` form events as an example. -## Locally Testing Page Views & Custom Events and/or Troubleshooting +### Locally Testing Page Views & Custom Events and/or Troubleshooting Adobe Analytics 1. Set the `REACT_APP_ADOBE_ANALYTICS_URL` environment variable in `.env`. 2. Use the browser tools Network tab, filter requests by "adobe", and check that successful network requests have been made to load the launch script and its extensions. 3. In the browser console, type `_satellite.setDebug(true)`. 4. Refresh the page. You should see Adobe debug log output in the console. Each page view change or custom event that fires should be visible in the logs. 5. When viewing dashboards in Adobe Analytics, it may take ~1 hour for analytics data to update. Once this happens, locally fired events will be visible in the dev dashboard. + +## Pendo + +Cloud Manager uses [Pendo](https://www.pendo.io/pendo-for-your-customers/) to capture analytics, guide users, and improve the user experience. To view Pendo dashboards, Cloud Manager developers must follow internal processes to request access. + +### Set Up and Initialization + +Pendo is configured in [`usePendo.js`](https://github.com/linode/manager/blob/develop/packages/manager/src/hooks/usePendo.ts). This custom hook allows us to initialize the Pendo analytics script when the [App](https://github.com/linode/manager/blob/develop/packages/manager/src/App.tsx#L56) is mounted. + +Important notes: + +- Pendo is only loaded if a valid `PENDO_API_KEY` is configured as an environment variable. In our development, staging, and production environments, `PENDO_API_KEY` is available at build time. See **Locally Testing Page Views & Custom Events and/or Troubleshooting Pendo** for set up with local environments. +- We load the Pendo agent from the CDN, rather than [self-hosting](https://support.pendo.io/hc/en-us/articles/360038969692-Self-hosting-the-Pendo-agent). +- We are hashing account and visitor IDs in a way that is consistent with Akamai's standards. +- At initialization, we do string transformation on select URL patterns to **remove sensitive data**. When new URL patterns are added to Cloud Manager, verify that existing transforms remove sensitive data; if not, update the transforms. +- Pendo is currently not using any client-side (cookies or local) storage. +- Pendo makes use of the existing `data-testid` properties, used in our automated testing, for tagging elements. They are more persistent and reliable than CSS properties, which are liable to change. + +### Locally Testing Page Views & Custom Events and/or Troubleshooting Pendo + +1. Set the `REACT_APP_PENDO_API_KEY` environment variable in `.env`. +2. Use the browser tools Network tab, filter requests by "pendo", and check that successful network requests have been made to load Pendo scripts. (Also visible in browser tools Sources tab.) +3. In the browser console, type `pendo.validateEnvironment()`. +4. You should see command output in the console, and it should include a hashed `accountId` and hashed `visitorId`. Each page view change or custom event that fires should be visible as a request in the Network tab. diff --git a/packages/api-v4/.changeset/pr-11129-fixed-1729536266478.md b/packages/api-v4/.changeset/pr-11129-fixed-1729536266478.md new file mode 100644 index 00000000000..4a2d6de69a4 --- /dev/null +++ b/packages/api-v4/.changeset/pr-11129-fixed-1729536266478.md @@ -0,0 +1,5 @@ +--- +"@linode/api-v4": Fixed +--- + +Incorrect documentation on how to set a page size ([#11129](https://github.com/linode/manager/pull/11129)) diff --git a/packages/api-v4/README.md b/packages/api-v4/README.md index 4c3daea5d41..28c200284c2 100644 --- a/packages/api-v4/README.md +++ b/packages/api-v4/README.md @@ -141,7 +141,7 @@ pagination and filter parameters to the API: ```js // Return page 2 of Linodes, with a page size of 100: -getLinodes({ page: 2, pageSize: 100 }); +getLinodes({ page: 2, page_size: 100 }); // Return all public Linode Images: getImages({}, { is_public: true }); diff --git a/packages/manager/.changeset/pr-11122-added-1729183156879.md b/packages/manager/.changeset/pr-11122-added-1729183156879.md new file mode 100644 index 00000000000..307c7091080 --- /dev/null +++ b/packages/manager/.changeset/pr-11122-added-1729183156879.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Added +--- + +Pendo documentation to our development guide ([#11122](https://github.com/linode/manager/pull/11122)) diff --git a/packages/manager/.changeset/pr-11130-fixed-1729536728596.md b/packages/manager/.changeset/pr-11130-fixed-1729536728596.md new file mode 100644 index 00000000000..7dbedc2c63b --- /dev/null +++ b/packages/manager/.changeset/pr-11130-fixed-1729536728596.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Fixed +--- + +Flaky DatabaseBackups.test.tsx in coverage job ([#11130](https://github.com/linode/manager/pull/11130)) diff --git a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/DatabaseBackups.test.tsx b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/DatabaseBackups.test.tsx index 0abbd2a4cad..b482223dfc7 100644 --- a/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/DatabaseBackups.test.tsx +++ b/packages/manager/src/features/Databases/DatabaseDetail/DatabaseBackups/DatabaseBackups.test.tsx @@ -1,3 +1,4 @@ +import { waitFor } from '@testing-library/react'; import * as React from 'react'; import { @@ -6,7 +7,7 @@ import { profileFactory, } from 'src/factories'; import { makeResourcePage } from 'src/mocks/serverHandlers'; -import { http, HttpResponse, server } from 'src/mocks/testServer'; +import { HttpResponse, http, server } from 'src/mocks/testServer'; import { formatDate } from 'src/utilities/formatDate'; import { renderWithTheme } from 'src/utilities/testHelpers'; @@ -19,7 +20,6 @@ describe('Database Backups', () => { }); const backups = databaseBackupFactory.buildList(7); - // Mock the Database because the Backups Details page requires it to be loaded server.use( http.get('*/profile', () => { return HttpResponse.json(profileFactory.build({ timezone: 'utc' })); @@ -32,15 +32,34 @@ describe('Database Backups', () => { }) ); - const { findByText } = renderWithTheme(); + const { findAllByText, getByText, queryByText } = renderWithTheme( + + ); + + // Wait for loading to disappear + await waitFor(() => + expect(queryByText(/loading/i)).not.toBeInTheDocument() + ); + + await waitFor( + async () => { + const renderedBackups = await findAllByText((content) => { + return /\d{4}-\d{2}-\d{2}/.test(content); + }); + expect(renderedBackups).toHaveLength(backups.length); + }, + { timeout: 5000 } + ); - for (const backup of backups) { - // Check to see if all 7 backups are rendered - expect( - // eslint-disable-next-line no-await-in-loop - await findByText(formatDate(backup.created, { timezone: 'utc' })) - ).toBeInTheDocument(); - } + await waitFor( + () => { + backups.forEach((backup) => { + const formattedDate = formatDate(backup.created, { timezone: 'utc' }); + expect(getByText(formattedDate)).toBeInTheDocument(); + }); + }, + { timeout: 5000 } + ); }); it('should render an empty state if there are no backups', async () => { diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx index 36c86b2c3dc..dcb02f65c7b 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Security.tsx @@ -1,4 +1,3 @@ -import { inputMaxWidth } from '@linode/ui'; import React from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; @@ -56,7 +55,11 @@ export const Security = () => { Security } + fallback={ + ({ height: '89px', maxWidth: theme.inputMaxWidth })} + /> + } > ( diff --git a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx index 483cb67c548..9c81cdb492f 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/VPC/VPC.tsx @@ -1,4 +1,3 @@ -import { inputMaxWidth } from '@linode/ui'; import React, { useState } from 'react'; import { Controller, useFormContext, useWatch } from 'react-hook-form'; @@ -133,7 +132,9 @@ export const VPC = () => { }} textFieldProps={{ sx: (theme) => ({ - [theme.breakpoints.up('sm')]: { minWidth: inputMaxWidth }, + [theme.breakpoints.up('sm')]: { + minWidth: theme.inputMaxWidth, + }, }), tooltipText: REGION_CAVEAT_HELPER_TEXT, }} diff --git a/packages/manager/src/hooks/usePendo.ts b/packages/manager/src/hooks/usePendo.ts index 0c109a4a3c9..898b5dc8567 100644 --- a/packages/manager/src/hooks/usePendo.ts +++ b/packages/manager/src/hooks/usePendo.ts @@ -30,7 +30,7 @@ const hashUniquePendoId = (id: string | undefined) => { }; /** - * Initializes our Pendo analytics script on mount. + * Initializes our Pendo analytics script on mount if a valid `PENDO_API_KEY` exists. */ export const usePendo = () => { const { data: account } = useAccount(); diff --git a/packages/ui/.changeset/pr-11116-changed-1729114321677.md b/packages/ui/.changeset/pr-11116-changed-1729114321677.md new file mode 100644 index 00000000000..adc6876ac5a --- /dev/null +++ b/packages/ui/.changeset/pr-11116-changed-1729114321677.md @@ -0,0 +1,5 @@ +--- +"@linode/ui": Changed +--- + +Moved `inputMaxWidth` into `Theme` ([#11116](https://github.com/linode/manager/pull/11116)) diff --git a/packages/ui/src/foundations/themes/index.ts b/packages/ui/src/foundations/themes/index.ts index c94d55ac8ae..4a507f9e6b1 100644 --- a/packages/ui/src/foundations/themes/index.ts +++ b/packages/ui/src/foundations/themes/index.ts @@ -3,7 +3,7 @@ import { deepmerge } from '@mui/utils'; // Themes & Brands import { darkTheme } from './dark'; -import { lightTheme, inputMaxWidth as _inputMaxWidth } from './light'; +import { lightTheme } from './light'; import type { ChartTypes, @@ -77,6 +77,7 @@ declare module '@mui/material/styles/createTheme' { color: Colors; font: Fonts; graphs: any; + inputMaxWidth: number; inputStyles: any; interactionTokens: InteractionTypes; name: ThemeName; @@ -97,6 +98,7 @@ declare module '@mui/material/styles/createTheme' { color?: DarkModeColors | LightModeColors; font?: Fonts; graphs?: any; + inputMaxWidth?: number; inputStyles?: any; interactionTokens?: InteractionTypes; name: ThemeName; @@ -106,6 +108,5 @@ declare module '@mui/material/styles/createTheme' { } } -export const inputMaxWidth = _inputMaxWidth; export const light = createTheme(lightTheme); export const dark = createTheme(deepmerge(lightTheme, darkTheme)); diff --git a/packages/ui/src/foundations/themes/light.ts b/packages/ui/src/foundations/themes/light.ts index b42e5ade10d..c7f36351839 100644 --- a/packages/ui/src/foundations/themes/light.ts +++ b/packages/ui/src/foundations/themes/light.ts @@ -15,7 +15,7 @@ import { latoWeb } from '../fonts'; import type { ThemeOptions } from '@mui/material/styles'; -export const inputMaxWidth = 416; +const inputMaxWidth = 416; export const bg = { app: Color.Neutrals[5], @@ -1526,6 +1526,7 @@ export const lightTheme: ThemeOptions = { }, yellow: `rgba(255, 220, 125, ${graphTransparency})`, }, + inputMaxWidth, inputStyles: { default: { backgroundColor: Select.Default.Background,