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:
parent
89fbde996f
commit
6ebf676b5e
@ -29,7 +29,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph/shared';
|
||||
import {
|
||||
@ -295,24 +295,23 @@ 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;
|
||||
}
|
||||
projectGraphService.send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
dependencies: response.dependencies,
|
||||
fileMap: response.fileMap,
|
||||
});
|
||||
setLastHash(response.hash);
|
||||
);
|
||||
if (response.hash === lastHash) {
|
||||
return;
|
||||
}
|
||||
projectGraphService.send({
|
||||
type: 'updateGraph',
|
||||
projects: response.projects,
|
||||
dependencies: response.dependencies,
|
||||
fileMap: response.fileMap,
|
||||
});
|
||||
setLastHash(response.hash);
|
||||
},
|
||||
5000,
|
||||
environmentConfig.watch
|
||||
|
||||
@ -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);
|
||||
});
|
||||
);
|
||||
setErrors(response.errors);
|
||||
},
|
||||
1000,
|
||||
environmentConfig.watch
|
||||
|
||||
@ -3,7 +3,7 @@ import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
useEnvironmentConfig,
|
||||
useIntervalWhen,
|
||||
usePoll,
|
||||
} from '@nx/graph/shared';
|
||||
import { ErrorRenderer } from '@nx/graph/ui-components';
|
||||
import {
|
||||
@ -23,20 +23,20 @@ export function ErrorBoundary() {
|
||||
const hasErrorData =
|
||||
isRouteErrorResponse(error) && error.data.errors?.length > 0;
|
||||
|
||||
useIntervalWhen(
|
||||
usePoll(
|
||||
async () => {
|
||||
fetchProjectGraph(projectGraphDataService, params, appConfig).then(
|
||||
(data) => {
|
||||
if (
|
||||
isRouteErrorResponse(error) &&
|
||||
error.data.id === 'project-not-found' &&
|
||||
data.projects.find((p) => p.name === error.data.projectName)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
appConfig
|
||||
);
|
||||
if (
|
||||
isRouteErrorResponse(error) &&
|
||||
error.data.id === 'project-not-found' &&
|
||||
data.projects.find((p) => p.name === error.data.projectName)
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
watch
|
||||
|
||||
@ -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) => {
|
||||
if (data?.hash !== hash) {
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await fetchProjectGraph(
|
||||
projectGraphDataService,
|
||||
params,
|
||||
appConfig
|
||||
);
|
||||
if (data?.hash !== hash) {
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
1000,
|
||||
watch
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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]);
|
||||
};
|
||||
37
graph/shared/src/lib/use-poll.ts
Normal file
37
graph/shared/src/lib/use-poll.ts
Normal 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]);
|
||||
};
|
||||
@ -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') {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
onClick={(e) => {
|
||||
if (isCollasped) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
>
|
||||
<EyeIcon
|
||||
className={`h-5 w-5 !cursor-pointer`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
<EyeIcon className={`h-5 w-5 !cursor-pointer`} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
@ -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
28
pnpm-lock.yaml
generated
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user