fix(graph): fix search params reload when back to graph (#26580)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
- change useIntervalWhen
- when this function is used, it is mostly calling a fetch request,
change it to take an async function
- wait for the current fetch request to finish before firing the next
one
  - change the name to usePoll
- currently, when going back to graph, it needs to remove the search
params expand and sometimes need to add ?project= search params
- delete expand search params is called after adding the project search
params
  
```

 setSearchParams(
        (currentSearchParams) => {
          currentSearchParams.delete('expanded');
          return currentSearchParams;
        },
        { replace: true, preventScrollReset: true }
      );

```


this function does not contain the ?project= search param key, caused
the graph page to reload twice
- change the function useRouteConstructor to delete the search params
`expanded` there


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Emily Xiong 2024-06-25 07:05:05 -07:00 committed by GitHub
parent 89fbde996f
commit 6ebf676b5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 125 additions and 111 deletions

View File

@ -29,7 +29,7 @@ import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
usePoll,
useRouteConstructor,
} from '@nx/graph/shared';
import {
@ -295,13 +295,13 @@ export function ProjectsSidebar(): JSX.Element {
}
}, [searchParams]);
useIntervalWhen(
() => {
fetchProjectGraph(
usePoll(
async () => {
const response: ProjectGraphClientResponse = await fetchProjectGraph(
projectGraphDataService,
params,
environmentConfig.appConfig
).then((response: ProjectGraphClientResponse) => {
);
if (response.hash === lastHash) {
return;
}
@ -312,7 +312,6 @@ export function ProjectsSidebar(): JSX.Element {
fileMap: response.fileMap,
});
setLastHash(response.hash);
});
},
5000,
environmentConfig.watch

View File

@ -16,7 +16,7 @@ import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
usePoll,
} from '@nx/graph/shared';
import { Dropdown, Spinner } from '@nx/graph/ui-components';
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph/ui-theme';
@ -71,15 +71,15 @@ export function Shell(): JSX.Element {
useLayoutEffect(() => {
setErrors(routerErrors);
}, [routerErrors]);
useIntervalWhen(
() => {
fetchProjectGraph(
usePoll(
async () => {
const response: ProjectGraphClientResponse = await fetchProjectGraph(
projectGraphDataService,
params,
environmentConfig.appConfig
).then((response: ProjectGraphClientResponse) => {
);
setErrors(response.errors);
});
},
1000,
environmentConfig.watch

View File

@ -3,7 +3,7 @@ import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
usePoll,
} from '@nx/graph/shared';
import { ErrorRenderer } from '@nx/graph/ui-components';
import {
@ -23,10 +23,13 @@ export function ErrorBoundary() {
const hasErrorData =
isRouteErrorResponse(error) && error.data.errors?.length > 0;
useIntervalWhen(
usePoll(
async () => {
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
(data) => {
const data = await fetchProjectGraph(
projectGraphDataService,
params,
appConfig
);
if (
isRouteErrorResponse(error) &&
error.data.id === 'project-not-found' &&
@ -34,9 +37,6 @@ export function ErrorBoundary() {
) {
window.location.reload();
}
return;
}
);
},
1000,
watch

View File

@ -16,7 +16,7 @@ import {
fetchProjectGraph,
getProjectGraphDataService,
useEnvironmentConfig,
useIntervalWhen,
usePoll,
} from '@nx/graph/shared';
import { ProjectDetailsHeader } from './project-details-header';
@ -35,16 +35,16 @@ export function ProjectDetailsPage() {
const projectGraphDataService = getProjectGraphDataService();
const params = useParams();
useIntervalWhen(
usePoll(
async () => {
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
(data) => {
const data = await fetchProjectGraph(
projectGraphDataService,
params,
appConfig
);
if (data?.hash !== hash) {
window.location.reload();
}
return;
}
);
},
1000,
watch

View File

@ -50,7 +50,8 @@ export function ProjectDetailsWrapper({
navigate(
routeConstructor(
`/projects/${encodeURIComponent(data.projectName)}`,
true
true,
['expanded'] // omit expanded targets from search params
)
);
}
@ -75,7 +76,8 @@ export function ProjectDetailsWrapper({
pathname: `/tasks/${encodeURIComponent(data.targetName)}`,
search: `?projects=${encodeURIComponent(data.projectName)}`,
},
true
true,
['expanded'] // omit expanded targets from search params
)
);
}
@ -95,9 +97,9 @@ export function ProjectDetailsWrapper({
const updateSearchParams = (
params: URLSearchParams,
targetNames: string[]
targetNames?: string[]
) => {
if (targetNames.length === 0) {
if (!targetNames || targetNames.length === 0) {
params.delete('expanded');
} else {
params.set('expanded', targetNames.join(','));
@ -118,13 +120,6 @@ export function ProjectDetailsWrapper({
if (collapseAllTargets) {
collapseAllTargets();
}
setSearchParams(
(currentSearchParams) => {
currentSearchParams.delete('expanded');
return currentSearchParams;
},
{ replace: true, preventScrollReset: true }
);
};
}, []); // only run on mount
@ -132,7 +127,7 @@ export function ProjectDetailsWrapper({
const expandedTargetsParams =
searchParams.get('expanded')?.split(',') || [];
if (expandedTargetsParams.join(',') === expandedTargets.join(',')) {
if (expandedTargetsParams.join(',') === expandedTargets?.join(',')) {
return;
}

View File

@ -3,7 +3,7 @@ export * from './lib/external-api-service';
export * from './lib/use-environment-config';
export * from './lib/app-config';
export * from './lib/use-route-constructor';
export * from './lib/use-interval-when';
export * from './lib/use-poll';
export * from './lib/project-graph-data-service/get-project-graph-data-service';
export * from './lib/fetch-project-graph';
export * from './lib/error-toast';

View File

@ -1,28 +0,0 @@
import { useEffect, useRef } from 'react';
export const useIntervalWhen = (
callback: () => void,
delay: number,
condition: boolean
) => {
const savedCallback = useRef(() => {});
useEffect(() => {
if (condition) {
savedCallback.current = callback;
}
}, [callback, condition]);
useEffect(() => {
if (condition) {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}
}, [delay, condition]);
};

View File

@ -0,0 +1,37 @@
import { useEffect, useRef } from 'react';
export const usePoll = (
callback: () => Promise<void>,
delay: number,
condition: boolean
) => {
const savedCallback = useRef(() => Promise.resolve());
useEffect(() => {
if (condition) {
savedCallback.current = callback;
}
}, [callback, condition]);
useEffect(() => {
if (!condition) {
return;
}
let timeoutId: NodeJS.Timeout;
async function callTickAfterDelay() {
await savedCallback.current();
if (delay !== null) {
timeoutId = setTimeout(callTickAfterDelay, delay);
}
}
callTickAfterDelay();
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [delay, condition]);
};

View File

@ -3,13 +3,23 @@ import { getEnvironmentConfig } from './use-environment-config';
export const useRouteConstructor = (): ((
to: To,
retainSearchParams: boolean
retainSearchParams: boolean,
searchParamsKeysToOmit?: string[]
) => To) => {
const { environment } = getEnvironmentConfig();
const { selectedWorkspaceId } = useParams();
const [searchParams] = useSearchParams();
return (to: To, retainSearchParams: true) => {
return (
to: To,
retainSearchParams: boolean = true,
searchParamsKeysToOmit: string[] = []
) => {
if (searchParamsKeysToOmit?.length) {
searchParamsKeysToOmit.forEach((key) => {
searchParams.delete(key);
});
}
let pathname = '';
if (typeof to === 'object') {

View File

@ -37,8 +37,8 @@ export class GraphTooltipService {
if (graph.getTaskInputs) {
graph.getTaskInputs(event.data.id).then((inputs) => {
if (
this.currentTooltip.type === 'taskNode' &&
this.currentTooltip.props.id === event.data.id
this.currentTooltip?.type === 'taskNode' &&
this.currentTooltip?.props.id === event.data.id
) {
this.openTaskNodeTooltip(event.ref, {
...event.data,

View File

@ -129,14 +129,15 @@ export const TargetConfigurationDetailsHeader = ({
// TODO: fix tooltip overflow in collapsed state
data-tooltip={isCollasped ? false : 'View in Task Graph'}
data-tooltip-align-right
>
<EyeIcon
className={`h-5 w-5 !cursor-pointer`}
onClick={(e) => {
if (isCollasped) {
return;
}
e.stopPropagation();
onViewInTaskGraph({ projectName, targetName });
}}
/>
>
<EyeIcon className={`h-5 w-5 !cursor-pointer`} />
</button>
)}

View File

@ -249,7 +249,7 @@
"react-markdown": "^8.0.7",
"react-redux": "8.0.5",
"react-refresh": "^0.10.0",
"react-router-dom": "^6.21.2",
"react-router-dom": "^6.23.1",
"react-textarea-autosize": "^8.5.3",
"regenerator-runtime": "0.13.7",
"resolve.exports": "1.1.0",

28
pnpm-lock.yaml generated
View File

@ -822,8 +822,8 @@ devDependencies:
specifier: ^0.10.0
version: 0.10.0
react-router-dom:
specifier: ^6.21.2
version: 6.21.2(react-dom@18.3.1)(react@18.3.1)
specifier: ^6.23.1
version: 6.23.1(react-dom@18.3.1)(react@18.3.1)
react-textarea-autosize:
specifier: ^8.5.3
version: 8.5.3(@types/react@18.3.1)(react@18.3.1)
@ -12051,11 +12051,6 @@ packages:
typescript: 5.4.2
dev: true
/@remix-run/router@1.14.2:
resolution: {integrity: sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg==}
engines: {node: '>=14.0.0'}
dev: true
/@remix-run/router@1.15.3:
resolution: {integrity: sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==}
engines: {node: '>=14.0.0'}
@ -12066,6 +12061,11 @@ packages:
engines: {node: '>=14.0.0'}
dev: true
/@remix-run/router@1.16.1:
resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==}
engines: {node: '>=14.0.0'}
dev: true
/@remix-run/server-runtime@2.8.1(typescript@5.4.2):
resolution: {integrity: sha512-fh4SOEoONrN73Kvzc0gMDCmYpVRVbvoj9j3BUXHAcn0An8iX+HD/22gU7nTkIBzExM/F9xgEcwTewOnWqLw0Bg==}
engines: {node: '>=18.0.0'}
@ -30502,26 +30502,26 @@ packages:
use-sidecar: 1.1.2(@types/react@18.3.1)(react@18.3.1)
dev: true
/react-router-dom@6.21.2(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-tE13UukgUOh2/sqYr6jPzZTzmzc70aGRP4pAjG2if0IP3aUT+sBtAKUJh0qMh0zylJHGLmzS+XWVaON4UklHeg==}
/react-router-dom@6.23.1(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
react-dom: '>=16.8'
dependencies:
'@remix-run/router': 1.14.2
'@remix-run/router': 1.16.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-router: 6.21.2(react@18.3.1)
react-router: 6.23.1(react@18.3.1)
dev: true
/react-router@6.21.2(react@18.3.1):
resolution: {integrity: sha512-jJcgiwDsnaHIeC+IN7atO0XiSRCrOsQAHHbChtJxmgqG2IaYQXSnhqGb5vk2CU/wBQA12Zt+TkbuJjIn65gzbA==}
/react-router@6.23.1(react@18.3.1):
resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
react: '>=16.8'
dependencies:
'@remix-run/router': 1.14.2
'@remix-run/router': 1.16.1
react: 18.3.1
dev: true