diff --git a/js_modules/dagster-ui/packages/app-oss/package.json b/js_modules/dagster-ui/packages/app-oss/package.json index 1a66dbddeba87..6b880703f5e2f 100644 --- a/js_modules/dagster-ui/packages/app-oss/package.json +++ b/js_modules/dagster-ui/packages/app-oss/package.json @@ -22,6 +22,7 @@ "react-router": "^5.2.1", "react-router-dom": "^5.3.0", "react-router-dom-v5-compat": "^6.3.0", + "recoil": "^0.7.7", "styled-components": "^5.3.3", "uuid": "^9.0.0", "validator": "^13.7.0", diff --git a/js_modules/dagster-ui/packages/app-oss/src/App.tsx b/js_modules/dagster-ui/packages/app-oss/src/App.tsx index ea164669840a7..7533e97739d11 100644 --- a/js_modules/dagster-ui/packages/app-oss/src/App.tsx +++ b/js_modules/dagster-ui/packages/app-oss/src/App.tsx @@ -10,6 +10,7 @@ import {logLink, timeStartLink} from '@dagster-io/ui-core/app/apolloLinks'; import {DeploymentStatusType} from '@dagster-io/ui-core/instance/DeploymentStatusProvider'; import {LiveDataPollRateContext} from '@dagster-io/ui-core/live-data-provider/LiveDataProvider'; import {Suspense} from 'react'; +import {RecoilRoot} from 'recoil'; import {InjectedComponents} from './InjectedComponents'; import {CommunityNux} from './NUX/CommunityNux'; @@ -40,21 +41,23 @@ const appCache = createAppCache(); // eslint-disable-next-line import/no-default-export export default function AppPage() { return ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + ); } diff --git a/js_modules/dagster-ui/packages/ui-core/package.json b/js_modules/dagster-ui/packages/ui-core/package.json index 02d34ef6f190c..d4a10b3dcb97b 100644 --- a/js_modules/dagster-ui/packages/ui-core/package.json +++ b/js_modules/dagster-ui/packages/ui-core/package.json @@ -62,6 +62,7 @@ "react-is": "^18.2.0", "react-markdown": "^8.0.6", "react-virtualized": "^9.22.3", + "recoil": "^0.7.7", "rehype-highlight": "^6.0.0", "rehype-sanitize": "^5.0.1", "remark": "^14.0.2", diff --git a/js_modules/dagster-ui/packages/ui-core/src/app/__tests__/analytics.test.tsx b/js_modules/dagster-ui/packages/ui-core/src/app/__tests__/analytics.test.tsx index d7251bc697b76..ae559965aeb15 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/app/__tests__/analytics.test.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/app/__tests__/analytics.test.tsx @@ -2,6 +2,7 @@ import {render, screen, waitFor} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import {MemoryRouter, Route, Switch} from 'react-router-dom'; +import {RecoilRoot} from 'recoil'; import {AnalyticsContext, GenericAnalytics, useTrackEvent, useTrackPageView} from '../analytics'; @@ -32,15 +33,17 @@ describe('Analytics', () => { const mockAnalytics = createMockAnalytics(); render( - - - - - - - - - , + + + + + + + + + + + , ); jest.advanceTimersByTime(400); @@ -68,15 +71,17 @@ describe('Analytics', () => { const mockAnalytics = createMockAnalytics(); render( - - - - - - - - - , + + + + + + + + + + + , ); const button = screen.getByRole('button'); @@ -103,17 +108,19 @@ describe('Analytics', () => { const overrideAnalytics = createMockAnalytics(); render( - - - - - - - - + + + + + + + + + + - - , + + , ); jest.advanceTimersByTime(400); diff --git a/js_modules/dagster-ui/packages/ui-core/src/app/analytics.tsx b/js_modules/dagster-ui/packages/ui-core/src/app/analytics.tsx index 54d06c8652d9e..422ab08e65202 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/app/analytics.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/app/analytics.tsx @@ -1,5 +1,11 @@ -import {createContext, useCallback, useContext, useEffect, useMemo} from 'react'; +import {createContext, useCallback, useContext, useLayoutEffect} from 'react'; import {useLocation, useRouteMatch} from 'react-router-dom'; +import {atom, useRecoilValue, useSetRecoilState} from 'recoil'; + +export const currentPageAtom = atom<{path: string; specificPath: string}>({ + key: 'currentPageAtom', + default: {path: '/', specificPath: '/'}, +}); export interface GenericAnalytics { group?: (groupId: string, traits?: Record) => void; @@ -13,10 +19,7 @@ export const AnalyticsContext = createContext(undefined!); const PAGEVIEW_DELAY = 300; export const usePageContext = () => { - const match = useRouteMatch(); - const {pathname: specificPath} = useLocation(); - const {path} = match; - return useMemo(() => ({path, specificPath}), [path, specificPath]); + return useRecoilValue(currentPageAtom); }; const useAnalytics = () => { @@ -52,28 +55,35 @@ export const dummyAnalytics = () => ({ export const useTrackPageView = () => { const analytics = useAnalytics(); - const {path, specificPath} = usePageContext(); + const match = useRouteMatch(); + const {pathname: specificPath} = useLocation(); + const {path} = match; + + const setCurrentPage = useSetRecoilState(currentPageAtom); - useEffect(() => { + useLayoutEffect(() => { // Wait briefly to allow redirects. const timer = setTimeout(() => { analytics.page(path, specificPath); }, PAGEVIEW_DELAY); + setCurrentPage({path, specificPath}); return () => { clearTimeout(timer); }; - }, [analytics, path, specificPath]); + }, [analytics, path, setCurrentPage, specificPath]); }; export const useTrackEvent = () => { const analytics = useAnalytics(); - const pathValues = usePageContext(); + const match = useRouteMatch(); + const {pathname: specificPath} = useLocation(); + const {path} = match; return useCallback( (eventName: string, properties?: Record) => { - analytics.track(eventName, {...properties, ...pathValues}); + analytics.track(eventName, {...properties, path, specificPath}); }, - [analytics, pathValues], + [analytics, path, specificPath], ); }; diff --git a/js_modules/dagster-ui/packages/ui-core/src/instance/backfill/__tests__/BackfillPage.test.tsx b/js_modules/dagster-ui/packages/ui-core/src/instance/backfill/__tests__/BackfillPage.test.tsx index 6b96256d21108..f66a41e1c5f17 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/instance/backfill/__tests__/BackfillPage.test.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/instance/backfill/__tests__/BackfillPage.test.tsx @@ -1,6 +1,7 @@ import {MockedProvider} from '@apollo/client/testing'; import {getAllByText, getByText, render, screen, waitFor} from '@testing-library/react'; import {MemoryRouter, Route} from 'react-router-dom'; +import {RecoilRoot} from 'recoil'; import {AnalyticsContext} from '../../../app/analytics'; import { @@ -82,15 +83,17 @@ const mocks = [ describe('BackfillPage', () => { it('renders the loading state', async () => { render( - {}} as any}> - - - - - - - - , + + {}} as any}> + + + + + + + + + , ); expect(await screen.findByTestId('page-loading-indicator')).toBeInTheDocument(); @@ -116,15 +119,17 @@ describe('BackfillPage', () => { ]; const {getByText} = render( - {}} as any}> - - - - - - - - , + + {}} as any}> + + + + + + + + + , ); await waitFor(() => expect(getByText('An error occurred')).toBeVisible()); @@ -132,15 +137,17 @@ describe('BackfillPage', () => { it('renders the loaded state', async () => { render( - {}} as any}> - - - - - - - - , + + {}} as any}> + + + + + + + + + , ); // Check if the loaded content is displayed diff --git a/js_modules/dagster-ui/packages/ui-core/src/testing/TestProvider.tsx b/js_modules/dagster-ui/packages/ui-core/src/testing/TestProvider.tsx index 9f9a1dc464b7f..c4fa454c51176 100644 --- a/js_modules/dagster-ui/packages/ui-core/src/testing/TestProvider.tsx +++ b/js_modules/dagster-ui/packages/ui-core/src/testing/TestProvider.tsx @@ -1,6 +1,7 @@ import {loader} from 'graphql.macro'; import * as React from 'react'; import {MemoryRouter, MemoryRouterProps} from 'react-router-dom'; +import {RecoilRoot} from 'recoil'; import {ApolloTestProps, ApolloTestProvider} from './ApolloTestProvider'; import {AppContext, AppContextValue} from '../app/AppContext'; @@ -73,25 +74,27 @@ export const TestProvider = (props: Props) => { ); return ( - - - - - - - {props.children} - - - - - - + + + + + + + + {props.children} + + + + + + + ); }; diff --git a/js_modules/dagster-ui/yarn.lock b/js_modules/dagster-ui/yarn.lock index 8fa65dd6c0740..2bb517650d27a 100644 --- a/js_modules/dagster-ui/yarn.lock +++ b/js_modules/dagster-ui/yarn.lock @@ -2406,6 +2406,7 @@ __metadata: react-router: "npm:^5.2.1" react-router-dom: "npm:^5.3.0" react-router-dom-v5-compat: "npm:^6.3.0" + recoil: "npm:^0.7.7" styled-components: "npm:^5.3.3" typescript: "npm:5.4.5" uuid: "npm:^9.0.0" @@ -2648,6 +2649,7 @@ __metadata: react-router-dom: "npm:^5.3.0" react-router-dom-v5-compat: "npm:^6.3.0" react-virtualized: "npm:^9.22.3" + recoil: "npm:^0.7.7" rehype-highlight: "npm:^6.0.0" rehype-sanitize: "npm:^5.0.1" remark: "npm:^14.0.2" @@ -14932,6 +14934,13 @@ __metadata: languageName: node linkType: hard +"hamt_plus@npm:1.0.2": + version: 1.0.2 + resolution: "hamt_plus@npm:1.0.2" + checksum: 10/3680a1820b8e03c79e5977edb7e1ccb48b9db853f5a1fbee2d3b3615f8dded7fa276e158f52d3eea8cb6192a27b2622ae2112e197be7602a8f99814793dc096f + languageName: node + linkType: hard + "handlebars@npm:^4.7.7": version: 4.7.8 resolution: "handlebars@npm:4.7.8" @@ -21288,6 +21297,22 @@ __metadata: languageName: node linkType: hard +"recoil@npm:^0.7.7": + version: 0.7.7 + resolution: "recoil@npm:0.7.7" + dependencies: + hamt_plus: "npm:1.0.2" + peerDependencies: + react: ">=16.13.1" + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + checksum: 10/4ac9dfeddda2795f213b14290de09767b04fb98f1a760bcabe796c53830229f51de63a02cf54c18991c557f25cfeb9571a129ae0a19d734bd98cfc45eebbc82d + languageName: node + linkType: hard + "redent@npm:^3.0.0": version: 3.0.0 resolution: "redent@npm:3.0.0"