aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web
diff options
context:
space:
mode:
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>2023-10-26 15:50:16 +0200
committersonartech <sonartech@sonarsource.com>2023-10-27 20:03:00 +0000
commit8619068f5c2641beef855e6a7cd412e19de271d8 (patch)
treea49e5dbe1d5172558898e657025280b87227c3f3 /server/sonar-web
parente2af95fe351114061ca3409ba1a9ba2abb58ab05 (diff)
downloadsonarqube-8619068f5c2641beef855e6a7cd412e19de271d8.tar.gz
sonarqube-8619068f5c2641beef855e6a7cd412e19de271d8.zip
SONAR-20447 Pass extra 'branch' parameter to the SonarLint call
Diffstat (limited to 'server/sonar-web')
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssueOpenInIdeButton-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx48
-rw-r--r--server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx75
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx4
-rw-r--r--server/sonar-web/src/main/js/helpers/__tests__/sonarlint-test.ts14
-rw-r--r--server/sonar-web/src/main/js/helpers/sonarlint.ts11
-rw-r--r--server/sonar-web/src/main/js/queries/branch.tsx9
10 files changed, 125 insertions, 52 deletions
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx
index 43b8f3c24f4..2c2a3faa56d 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssueOpenInIdeButton.tsx
@@ -33,6 +33,7 @@ import { openIssue as openSonarLintIssue, probeSonarLintServers } from '../../..
import { Ide } from '../../../types/sonarlint';
export interface Props {
+ branchName?: string;
issueKey: string;
projectKey: string;
}
@@ -46,7 +47,7 @@ const showError = () => addGlobalErrorMessage(translate('issues.open_in_ide.fail
const showSuccess = () => addGlobalSuccessMessage(translate('issues.open_in_ide.success'));
-export function IssueOpenInIdeButton({ issueKey, projectKey }: Readonly<Props>) {
+export function IssueOpenInIdeButton({ branchName, issueKey, projectKey }: Readonly<Props>) {
const [state, setState] = React.useState<State>({ ides: [], mounted: false });
React.useEffect(() => {
@@ -67,7 +68,7 @@ export function IssueOpenInIdeButton({ issueKey, projectKey }: Readonly<Props>)
const openIssue = (ide: Ide) => {
setState({ ...state, ides: [] });
- return openSonarLintIssue(ide.port, projectKey, issueKey)
+ return openSonarLintIssue(ide.port, projectKey, issueKey, branchName)
.then(showSuccess)
.catch(showError)
.finally(cleanState);
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssueOpenInIdeButton-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssueOpenInIdeButton-test.tsx
index aefd80d017f..f092b21b4d8 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssueOpenInIdeButton-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssueOpenInIdeButton-test.tsx
@@ -103,6 +103,7 @@ it('handles button click with one ide found', async () => {
MOCK_IDES[0].port,
MOCK_PROJECT_KEY,
MOCK_ISSUE_KEY,
+ undefined,
);
expect(addGlobalSuccessMessage).toHaveBeenCalledWith('issues.open_in_ide.success');
@@ -147,6 +148,7 @@ it('handles button click with several ides found', async () => {
MOCK_IDES[1].port,
MOCK_PROJECT_KEY,
MOCK_ISSUE_KEY,
+ undefined,
);
expect(addGlobalSuccessMessage).toHaveBeenCalledWith('issues.open_in_ide.success');
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx
index 880a385ae41..6505066761a 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/ComponentSourceSnippetGroupViewer.tsx
@@ -43,7 +43,7 @@ import {
Issue as TypeIssue,
} from '../../../types/types';
import { IssueSourceViewerScrollContext } from '../components/IssueSourceViewerScrollContext';
-import IssueSourceViewerHeader from './IssueSourceViewerHeader';
+import { IssueSourceViewerHeader } from './IssueSourceViewerHeader';
import SnippetViewer from './SnippetViewer';
import {
EXPAND_BY_LINES,
@@ -264,7 +264,7 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone
};
render() {
- const { branchLike, isLastOccurenceOfPrimaryComponent, issue, snippetGroup } = this.props;
+ const { isLastOccurenceOfPrimaryComponent, issue, snippetGroup } = this.props;
const { additionalLines, loading, snippets } = this.state;
const snippetLines = linesForSnippets(snippets, {
@@ -302,7 +302,6 @@ export default class ComponentSourceSnippetGroupViewer extends React.PureCompone
)}
<IssueSourceViewerHeader
- branchLike={branchLike}
className={issueIsClosed && !issueIsFileLevel ? 'null-spacer-bottom' : ''}
expandable={isExpandable(snippets, snippetGroup)}
issueKey={issue.key}
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
index 49b7d776a63..2f920ddb0f6 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import classNames from 'classnames';
import {
@@ -28,31 +29,28 @@ import {
LightLabel,
Link,
Spinner,
- ThemeProp,
UnfoldIcon,
themeColor,
- withTheme,
} from 'design-system';
import * as React from 'react';
-import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext';
+import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
+import { useCurrentUser } from '../../../app/components/current-user/CurrentUserContext';
import Tooltip from '../../../components/controls/Tooltip';
import { ClipboardBase } from '../../../components/controls/clipboard';
-import { getBranchLikeQuery } from '../../../helpers/branch-like';
+import { getBranchLikeQuery, isBranch, isPullRequest } from '../../../helpers/branch-like';
import { translate } from '../../../helpers/l10n';
import { collapsedDirFromPath, fileFromPath } from '../../../helpers/path';
import { getBranchLikeUrl, getComponentIssuesUrl } from '../../../helpers/urls';
-import { BranchLike } from '../../../types/branch-like';
+import { useBranchesQuery } from '../../../queries/branch';
import { ComponentQualifier } from '../../../types/component';
import { SourceViewerFile } from '../../../types/types';
-import { CurrentUser, isLoggedIn } from '../../../types/users';
+import { isLoggedIn } from '../../../types/users';
import { IssueOpenInIdeButton } from '../components/IssueOpenInIdeButton';
export const INTERACTIVE_TOOLTIP_DELAY = 0.5;
export interface Props {
- branchLike: BranchLike | undefined;
className?: string;
- currentUser: CurrentUser;
displayProjectName?: boolean;
expandable?: boolean;
issueKey: string;
@@ -62,11 +60,16 @@ export interface Props {
sourceViewerFile: SourceViewerFile;
}
-function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
+export function IssueSourceViewerHeader(props: Readonly<Props>) {
+ const { component } = React.useContext(ComponentContext);
+ const { data: branchData, isLoading: isLoadingBranches } = useBranchesQuery(component);
+ const currentUser = useCurrentUser();
+ const theme = useTheme();
+
+ const branchLike = branchData?.branchLike;
+
const {
- branchLike,
className,
- currentUser,
displayProjectName = true,
expandable,
issueKey,
@@ -74,7 +77,6 @@ function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
loading,
onExpand,
sourceViewerFile,
- theme,
} = props;
const { measures, path, project, projectName, q } = sourceViewerFile;
@@ -88,6 +90,18 @@ function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
border-bottom: none;
`;
+ const branchName = React.useMemo(() => {
+ if (isBranch(branchLike)) {
+ return branchLike.name;
+ }
+
+ if (isPullRequest(branchLike)) {
+ return branchLike.branch;
+ }
+
+ return undefined; // should never end up here, but needed for consistent returns
+ }, [branchLike]);
+
return (
<IssueSourceViewerStyle
aria-label={sourceViewerFile.path}
@@ -149,13 +163,13 @@ function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
</div>
{!isProjectRoot && isLoggedIn(currentUser) && (
- <IssueOpenInIdeButton issueKey={issueKey} projectKey={project} />
+ <IssueOpenInIdeButton branchName={branchName} issueKey={issueKey} projectKey={project} />
)}
{!isProjectRoot && measures.issues !== undefined && (
<div
className={classNames('sw-ml-4', {
- 'sw-mr-1': !expandable || loading,
+ 'sw-mr-1': (!expandable || loading) ?? isLoadingBranches,
})}
>
<Link
@@ -170,9 +184,9 @@ function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
</div>
)}
- <Spinner className="sw-mr-1" loading={loading} />
+ <Spinner className="sw-mr-1" loading={loading ?? isLoadingBranches} />
- {expandable && !loading && (
+ {expandable && !(loading ?? isLoadingBranches) && (
<div className="sw-ml-4">
<InteractiveIcon
Icon={UnfoldIcon}
@@ -185,5 +199,3 @@ function IssueSourceViewerHeader(props: Readonly<Props> & ThemeProp) {
</IssueSourceViewerStyle>
);
}
-
-export default withCurrentUserContext(withTheme(IssueSourceViewerHeader));
diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx
index 484f4c3b187..4121a99e936 100644
--- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/IssueSourceViewerHeader-test.tsx
@@ -17,12 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { screen, waitFor } from '@testing-library/react';
import * as React from 'react';
-import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
+import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
+import { AvailableFeaturesContext } from '../../../../app/components/available-features/AvailableFeaturesContext';
+import { ComponentContext } from '../../../../app/components/componentContext/ComponentContext';
+import { mockComponent } from '../../../../helpers/mocks/component';
import { mockSourceViewerFile } from '../../../../helpers/mocks/sources';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { byRole, byText } from '../../../../helpers/testSelector';
-import IssueSourceViewerHeader, { Props } from '../IssueSourceViewerHeader';
+import { Feature } from '../../../../types/features';
+import { IssueSourceViewerHeader, Props } from '../IssueSourceViewerHeader';
const ui = {
expandAllLines: byRole('button', { name: 'source_viewer.expand_all_lines' }),
@@ -31,54 +37,87 @@ const ui = {
viewAllIssues: byRole('link', { name: 'source_viewer.view_all_issues' }),
};
-it('should render correctly', () => {
+const branchHandler = new BranchesServiceMock();
+
+afterEach(() => {
+ branchHandler.reset();
+});
+
+it('should render correctly', async () => {
+ branchHandler.emptyBranchesAndPullRequest();
+
renderIssueSourceViewerHeader();
+ expect(await screen.findByText('loading')).toBeInTheDocument();
+ await waitFor(() => expect(screen.queryByText('loading')).not.toBeInTheDocument());
+
expect(ui.expandAllLines.get()).toBeInTheDocument();
expect(ui.projectLink.get()).toBeInTheDocument();
expect(ui.projectName.get()).toBeInTheDocument();
expect(ui.viewAllIssues.get()).toBeInTheDocument();
});
-it('should not render expandable link', () => {
+it('should not render expandable link', async () => {
renderIssueSourceViewerHeader({ expandable: false });
+ expect(await screen.findByText('loading')).toBeInTheDocument();
+ await waitFor(() => expect(screen.queryByText('loading')).not.toBeInTheDocument());
+
expect(ui.expandAllLines.query()).not.toBeInTheDocument();
});
-it('should not render link to project', () => {
- renderIssueSourceViewerHeader({ linkToProject: false });
+it('should not render link to project', async () => {
+ renderIssueSourceViewerHeader({ linkToProject: false }, '?id=my-project&branch=normal-branch');
+
+ expect(await screen.findByText('loading')).toBeInTheDocument();
+ await waitFor(() => expect(screen.queryByText('loading')).not.toBeInTheDocument());
expect(ui.projectLink.query()).not.toBeInTheDocument();
expect(ui.projectName.get()).toBeInTheDocument();
});
-it('should not render project name', () => {
- renderIssueSourceViewerHeader({ displayProjectName: false });
+it('should not render project name', async () => {
+ renderIssueSourceViewerHeader({ displayProjectName: false }, '?id=my-project&pullRequest=01');
+
+ expect(await screen.findByText('loading')).toBeInTheDocument();
+ await waitFor(() => expect(screen.queryByText('loading')).not.toBeInTheDocument());
expect(ui.projectLink.query()).not.toBeInTheDocument();
expect(ui.projectName.query()).not.toBeInTheDocument();
});
-it('should render without issue expand all when no issue', () => {
+it('should render without issue expand all when no issue', async () => {
renderIssueSourceViewerHeader({
sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
measures: {},
}),
});
+ expect(await screen.findByText('loading')).toBeInTheDocument();
+ await waitFor(() => expect(screen.queryByText('loading')).not.toBeInTheDocument());
+
expect(ui.viewAllIssues.query()).not.toBeInTheDocument();
});
-function renderIssueSourceViewerHeader(props: Partial<Props> = {}) {
+function renderIssueSourceViewerHeader(props: Partial<Props> = {}, path = '?id=my-project') {
return renderComponent(
- <IssueSourceViewerHeader
- branchLike={mockMainBranch()}
- expandable
- issueKey="issue-key"
- onExpand={jest.fn()}
- sourceViewerFile={mockSourceViewerFile('foo/bar.ts', 'my-project')}
- {...props}
- />,
+ <AvailableFeaturesContext.Provider value={[Feature.BranchSupport]}>
+ <ComponentContext.Provider
+ value={{
+ component: mockComponent(),
+ onComponentChange: jest.fn(),
+ fetchComponent: jest.fn(),
+ }}
+ >
+ <IssueSourceViewerHeader
+ expandable
+ issueKey="issue-key"
+ onExpand={jest.fn()}
+ sourceViewerFile={mockSourceViewerFile('foo/bar.ts', 'my-project')}
+ {...props}
+ />
+ </ComponentContext.Provider>
+ </AvailableFeaturesContext.Provider>,
+ path,
);
}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
index 0579f88f06b..ec96cfcf2ef 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx
@@ -54,7 +54,7 @@ export interface HotspotHeaderProps {
export function HotspotHeader(props: HotspotHeaderProps) {
const { branchLike, component, hotspot, standards } = props;
const { message, messageFormattings, rule, key } = hotspot;
- const refrechBranchStatus = useRefreshBranchStatus();
+ const refreshBranchStatus = useRefreshBranchStatus();
const permalink = getPathUrlAsString(
getComponentSecurityHotspotsUrl(component.key, {
@@ -67,7 +67,7 @@ export function HotspotHeader(props: HotspotHeaderProps) {
const categoryStandard = standards?.[SecurityStandard.SONARSOURCE][rule.securityCategory]?.title;
const handleStatusChange = async (statusOption: HotspotStatusOption) => {
await props.onUpdateHotspot(true, statusOption);
- refrechBranchStatus();
+ refreshBranchStatus();
};
return (
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
index 7594a22dfaf..216487db516 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
@@ -81,7 +81,7 @@ export default function HotspotViewerTabs(props: Props) {
branchLike,
} = props;
- const refrechBranchStatus = useRefreshBranchStatus();
+ const refreshBranchStatus = useRefreshBranchStatus();
const isSticky = useStickyDetection('.hotspot-tabs', {
offset: TABS_OFFSET,
});
@@ -163,7 +163,7 @@ export default function HotspotViewerTabs(props: Props) {
const handleStatusChange = async (statusOption: HotspotStatusOption) => {
await props.onUpdateHotspot(true, statusOption);
- refrechBranchStatus();
+ refreshBranchStatus();
};
React.useEffect(() => {
diff --git a/server/sonar-web/src/main/js/helpers/__tests__/sonarlint-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/sonarlint-test.ts
index ca8d93eaa8d..6db7f0bb4a0 100644
--- a/server/sonar-web/src/main/js/helpers/__tests__/sonarlint-test.ts
+++ b/server/sonar-web/src/main/js/helpers/__tests__/sonarlint-test.ts
@@ -95,6 +95,8 @@ describe('openHotspot', () => {
describe('openIssue', () => {
it('should send the correct request to the IDE to open an issue', async () => {
+ let branchName: string | undefined = undefined;
+ const issueKey = 'my-issue-key';
const resp = new Response();
window.fetch = jest.fn((input: RequestInfo) => {
@@ -103,7 +105,9 @@ describe('openIssue', () => {
try {
expect(calledUrl.searchParams.get('server')).toStrictEqual('http://localhost');
expect(calledUrl.searchParams.get('project')).toStrictEqual(PROJECT_KEY);
- expect(calledUrl.searchParams.get('issue')).toStrictEqual('my-issue-key');
+ expect(calledUrl.searchParams.get('issue')).toStrictEqual(issueKey);
+ // eslint-disable-next-line jest/no-conditional-in-test
+ expect(calledUrl.searchParams.get('branch') ?? undefined).toStrictEqual(branchName);
} catch (error) {
return Promise.reject(error);
}
@@ -111,7 +115,13 @@ describe('openIssue', () => {
return Promise.resolve(resp);
});
- const result = await openIssue(SONARLINT_PORT_START, PROJECT_KEY, 'my-issue-key');
+ let result = await openIssue(SONARLINT_PORT_START, PROJECT_KEY, issueKey);
+
+ expect(result).toBe(resp);
+
+ branchName = 'branch-1';
+
+ result = await openIssue(SONARLINT_PORT_START, PROJECT_KEY, issueKey, branchName);
expect(result).toBe(resp);
});
diff --git a/server/sonar-web/src/main/js/helpers/sonarlint.ts b/server/sonar-web/src/main/js/helpers/sonarlint.ts
index a9c378d491e..b4d9dbccd33 100644
--- a/server/sonar-web/src/main/js/helpers/sonarlint.ts
+++ b/server/sonar-web/src/main/js/helpers/sonarlint.ts
@@ -55,13 +55,22 @@ export function openHotspot(calledPort: number, projectKey: string, hotspotKey:
return fetch(showUrl.toString()).then((response: Response) => checkStatus(response, true));
}
-export function openIssue(calledPort: number, projectKey: string, issueKey: string) {
+export function openIssue(
+ calledPort: number,
+ projectKey: string,
+ issueKey: string,
+ branchName?: string,
+) {
const showUrl = new URL(buildSonarLintEndpoint(calledPort, '/issues/show'));
showUrl.searchParams.set('server', getHostUrl());
showUrl.searchParams.set('project', projectKey);
showUrl.searchParams.set('issue', issueKey);
+ if (branchName !== undefined) {
+ showUrl.searchParams.set('branch', branchName);
+ }
+
return fetch(showUrl.toString()).then((response: Response) => checkStatus(response, true));
}
diff --git a/server/sonar-web/src/main/js/queries/branch.tsx b/server/sonar-web/src/main/js/queries/branch.tsx
index 4f3a95be6cf..78bba188984 100644
--- a/server/sonar-web/src/main/js/queries/branch.tsx
+++ b/server/sonar-web/src/main/js/queries/branch.tsx
@@ -128,9 +128,10 @@ export function useBranchesQuery(component?: Component, refetchInterval?: number
: branchLikes.find(
(b) => isBranch(b) && (prOrBranch === 'branch' ? b.name === name : b.isMain),
);
+
return { branchLikes, branchLike };
},
- // The check of the key must desapear once component state is in react-query
+ // The check of the key must disappear once component state is in react-query
enabled: !!component && component.key === key[1],
staleTime: refetchInterval ?? BRANCHES_STALE_TIME,
refetchInterval,
@@ -293,10 +294,10 @@ export function useSetMainBranchMutation() {
}
/**
- * Helper functions that sould be avoid. Instead convert the component into functional
+ * Helper functions that sould be avoided. Instead convert the component into a functional one
* and/or use proper react-query
*/
-const DELAY_REFRECH = 1_000;
+const REFRESH_INTERVAL = 1_000;
export function useRefreshBranchStatus(): () => void {
const queryClient = useQueryClient();
@@ -311,7 +312,7 @@ export function useRefreshBranchStatus(): () => void {
queryClient.invalidateQueries({
queryKey: invalidateDetailsKey,
});
- }, DELAY_REFRECH),
+ }, REFRESH_INTERVAL),
[invalidateDetailsKey, invalidateStatusKey],
);
}