import { translate } from '../../../helpers/l10n';
import {
generateSonarLintUserToken,
- openIssue as openSonarLintIssue,
+ openFixOrIssueInSonarLint,
probeSonarLintServers,
} from '../../../helpers/sonarlint';
+import { BranchLike } from '../../../types/branch-like';
import { Ide } from '../../../types/sonarlint';
+import { NewUserToken } from '../../../types/token';
import { UserBase } from '../../../types/users';
export interface Props {
- branchName?: string;
+ branchLike?: BranchLike;
issueKey: string;
login: UserBase['login'];
projectKey: string;
const DELAY_AFTER_TOKEN_CREATION = 3000;
-export function IssueOpenInIdeButton({
- branchName,
- issueKey,
- login,
- projectKey,
- pullRequestID,
-}: Readonly<Props>) {
+export function IssueOpenInIdeButton({ branchLike, issueKey, login, projectKey }: Readonly<Props>) {
const [isDisabled, setIsDisabled] = React.useState(false);
const [ides, setIdes] = React.useState<Ide[] | undefined>(undefined);
const ref = React.useRef<HTMLButtonElement>(null);
const openIssue = async (ide: Ide) => {
setIsDisabled(true);
- let token: { name?: string; token?: string } = {};
+ let token: NewUserToken | undefined = undefined;
try {
if (ide.needsToken) {
token = await generateSonarLintUserToken({ ideName: ide.ideName, login });
}
- await openSonarLintIssue({
- branchName,
+ await openFixOrIssueInSonarLint({
+ branchLike,
calledPort: ide.port,
issueKey,
projectKey,
- pullRequestID,
- tokenName: token.name,
- tokenValue: token.token,
+ token,
});
showSuccess();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { Button, ButtonVariety, DropdownMenu } from '@sonarsource/echoes-react';
+import { addGlobalErrorMessage } from 'design-system/lib';
+import React, { useCallback, useState } from 'react';
+import { useCurrentUser } from '../../../app/components/current-user/CurrentUserContext';
+import { translate } from '../../../helpers/l10n';
+import { probeSonarLintServers } from '../../../helpers/sonarlint';
+import { useBranchesQuery } from '../../../queries/branch';
+import { useComponentForSourceViewer } from '../../../queries/component';
+import { CodeSuggestion } from '../../../queries/fix-suggestions';
+import { useOpenFixOrIssueInIdeMutation } from '../../../queries/sonarlint';
+import { Fix, Ide } from '../../../types/sonarlint';
+import { Issue } from '../../../types/types';
+
+export interface Props {
+ aiSuggestion: CodeSuggestion;
+ issue: Issue;
+}
+
+const DELAY_AFTER_TOKEN_CREATION = 3000;
+
+export function OpenFixInIde({ aiSuggestion, issue }: Readonly<Props>) {
+ const [ides, setIdes] = useState<Ide[]>([]);
+ const { data, isLoading: isBranchLoading } = useBranchesQuery();
+
+ const {
+ currentUser: { isLoggedIn },
+ } = useCurrentUser();
+
+ const { data: sourceViewerFile } = useComponentForSourceViewer(
+ issue.component,
+ data?.branchLike,
+ !isBranchLoading,
+ );
+ const { mutateAsync: openFixInIde, isPending } = useOpenFixOrIssueInIdeMutation();
+
+ const closeDropdown = () => {
+ setIdes([]);
+ };
+
+ const openFix = useCallback(
+ async (ide: Ide) => {
+ closeDropdown();
+
+ const fix: Fix = {
+ explanation: aiSuggestion.explanation,
+ fileEdit: {
+ changes: aiSuggestion.changes.map((change) => ({
+ after: change.newCode,
+ before: aiSuggestion.unifiedLines
+ .filter(
+ (line) => line.lineBefore >= change.startLine && line.lineBefore <= change.endLine,
+ )
+ .map((line) => line.code)
+ .join('\n'),
+ beforeLineRange: {
+ startLine: change.startLine,
+ endLine: change.endLine,
+ },
+ })),
+ path: sourceViewerFile?.path ?? '',
+ },
+ suggestionId: aiSuggestion.suggestionId,
+ };
+
+ await openFixInIde({
+ branchLike: data?.branchLike,
+ ide,
+ fix,
+ issue,
+ });
+
+ setTimeout(
+ () => {
+ closeDropdown();
+ },
+ ide.needsToken ? DELAY_AFTER_TOKEN_CREATION : 0,
+ );
+ },
+ [aiSuggestion, issue, sourceViewerFile, data, openFixInIde],
+ );
+
+ const onClick = async () => {
+ let IDEs = (await probeSonarLintServers()) ?? [];
+
+ IDEs = IDEs.filter((ide) => ide.capabilities?.canOpenFixSuggestion);
+
+ if (IDEs.length === 0) {
+ addGlobalErrorMessage(translate('unable_to_find_ide_with_fix.error'));
+ } else if (IDEs.length === 1) {
+ openFix(IDEs[0]);
+ } else {
+ setIdes(IDEs);
+ }
+ };
+
+ if (!isLoggedIn || data?.branchLike === undefined || sourceViewerFile === undefined) {
+ return null;
+ }
+
+ return (
+ <DropdownMenu.Root
+ items={ides.map((ide) => {
+ const { ideName, description } = ide;
+
+ const label = ideName + (description ? ` - ${description}` : '');
+
+ return (
+ <DropdownMenu.ItemButton
+ key={ide.port}
+ onClick={() => {
+ openFix(ide);
+ }}
+ >
+ {label}
+ </DropdownMenu.ItemButton>
+ );
+ })}
+ onClose={() => {
+ setIdes([]);
+ }}
+ onOpen={onClick}
+ >
+ <Button
+ className="sw-whitespace-nowrap"
+ isDisabled={isPending}
+ onClick={onClick}
+ variety={ButtonVariety.Default}
+ >
+ {translate('view_fix_in_ide')}
+ </Button>
+ </DropdownMenu.Root>
+ );
+}
import UserTokensMock from '../../../../api/mocks/UserTokensMock';
import DocumentationLink from '../../../../components/common/DocumentationLink';
import { DocLink } from '../../../../helpers/doc-links';
-import {
- openIssue as openSonarLintIssue,
- probeSonarLintServers,
-} from '../../../../helpers/sonarlint';
+import { openFixOrIssueInSonarLint, probeSonarLintServers } from '../../../../helpers/sonarlint';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { Ide } from '../../../../types/sonarlint';
import { IssueOpenInIdeButton, Props } from '../IssueOpenInIdeButton';
generateSonarLintUserToken: jest
.fn()
.mockResolvedValue({ name: 'token name', token: 'token value' }),
- openIssue: jest.fn().mockResolvedValue(undefined),
+ openFixOrIssueInSonarLint: jest.fn().mockResolvedValue(undefined),
probeSonarLintServers: jest.fn(),
}));
expect(addGlobalErrorMessage).not.toHaveBeenCalled();
expect(addGlobalSuccessMessage).not.toHaveBeenCalled();
- expect(openSonarLintIssue).not.toHaveBeenCalled();
+ expect(openFixOrIssueInSonarLint).not.toHaveBeenCalled();
expect(probeSonarLintServers).not.toHaveBeenCalled();
});
/>,
);
- expect(openSonarLintIssue).not.toHaveBeenCalled();
+ expect(openFixOrIssueInSonarLint).not.toHaveBeenCalled();
expect(addGlobalSuccessMessage).not.toHaveBeenCalled();
});
expect(probeSonarLintServers).toHaveBeenCalledWith();
- expect(openSonarLintIssue).toHaveBeenCalledWith({
+ expect(openFixOrIssueInSonarLint).toHaveBeenCalledWith({
branchName: undefined,
calledPort: MOCK_IDES[0].port,
issueKey: MOCK_ISSUE_KEY,
expect(probeSonarLintServers).toHaveBeenCalledWith();
- expect(openSonarLintIssue).not.toHaveBeenCalled();
+ expect(openFixOrIssueInSonarLint).not.toHaveBeenCalled();
expect(addGlobalSuccessMessage).not.toHaveBeenCalled();
expect(addGlobalErrorMessage).not.toHaveBeenCalled();
await user.click(secondIde);
- expect(openSonarLintIssue).toHaveBeenCalledWith({
- branchName: undefined,
+ expect(openFixOrIssueInSonarLint).toHaveBeenCalledWith({
+ branchLike: undefined,
calledPort: MOCK_IDES[1].port,
issueKey: MOCK_ISSUE_KEY,
projectKey: MOCK_PROJECT_KEY,
pullRequestID: undefined,
- tokenName: 'token name',
- tokenValue: 'token value',
+ token: {
+ name: 'token name',
+ token: 'token value',
+ },
});
expect(addGlobalSuccessMessage).toHaveBeenCalledWith('issues.open_in_ide.success');
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { useQueryClient } from '@tanstack/react-query';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React, { ComponentProps } from 'react';
+import BranchesServiceMock from '../../../../api/mocks/BranchesServiceMock';
+import { openFixOrIssueInSonarLint, probeSonarLintServers } from '../../../../helpers/sonarlint';
+import { mockIssue, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { CodeSuggestion, LineTypeEnum } from '../../../../queries/fix-suggestions';
+import { Fix, Ide } from '../../../../types/sonarlint';
+import { OpenFixInIde } from '../OpenFixInIde';
+
+jest.mock('../../../../api/components', () => ({
+ getComponentForSourceViewer: jest.fn().mockReturnValue({}),
+}));
+jest.mock('../../../../helpers/sonarlint', () => ({
+ generateSonarLintUserToken: jest
+ .fn()
+ .mockResolvedValue({ name: 'token name', token: 'token value' }),
+ openFixOrIssueInSonarLint: jest.fn().mockResolvedValue(undefined),
+ probeSonarLintServers: jest.fn(),
+}));
+
+const handler = new BranchesServiceMock();
+
+const MOCK_TOKEN: any = {
+ name: 'token name',
+ token: 'token value',
+};
+
+const FIX_DATA: Fix = {
+ explanation: 'explanation',
+ fileEdit: {
+ changes: [
+ {
+ after: 'var p = 2;',
+ before: 'var t = 1;',
+ beforeLineRange: {
+ startLine: 1,
+ endLine: 2,
+ },
+ },
+ ],
+ path: '',
+ },
+ suggestionId: 'suggestionId',
+};
+
+const AI_SUGGESTION: CodeSuggestion = {
+ changes: [{ endLine: 2, newCode: 'var p = 2;', startLine: 1 }],
+ explanation: 'explanation',
+ suggestionId: 'suggestionId',
+ unifiedLines: [
+ {
+ code: 'var t = 1;',
+ lineAfter: -1,
+ lineBefore: 1,
+ type: LineTypeEnum.REMOVED,
+ },
+ {
+ code: 'var p = 2;',
+ lineAfter: 1,
+ lineBefore: -1,
+ type: LineTypeEnum.ADDED,
+ },
+ ],
+};
+
+const MOCK_IDES_OPEN_FIX: Ide[] = [
+ {
+ description: 'IDE description',
+ ideName: 'Some IDE',
+ port: 1234,
+ capabilities: { canOpenFixSuggestion: true },
+ needsToken: false,
+ },
+ {
+ description: '',
+ ideName: 'Some other IDE',
+ needsToken: true,
+ port: 42000,
+ capabilities: { canOpenFixSuggestion: true },
+ },
+ { description: '', ideName: 'Some other IDE 2', needsToken: true, port: 43000 },
+];
+const MOCK_ISSUE_KEY = 'issue-key';
+const MOCK_PROJECT_KEY = 'project-key';
+
+beforeEach(() => {
+ handler.reset();
+});
+
+it('handles open in ide button click with several ides found when there is fix suggestion', async () => {
+ const user = userEvent.setup();
+
+ jest.mocked(probeSonarLintServers).mockResolvedValueOnce(MOCK_IDES_OPEN_FIX);
+
+ renderComponentOpenIssueInIdeButton();
+
+ await user.click(
+ await screen.findByRole('button', {
+ name: 'view_fix_in_ide',
+ }),
+ );
+
+ expect(
+ screen.getByRole('menuitem', {
+ name: `${MOCK_IDES_OPEN_FIX[0].ideName} - ${MOCK_IDES_OPEN_FIX[0].description}`,
+ }),
+ ).toBeInTheDocument();
+
+ const secondIde = screen.getByRole('menuitem', { name: MOCK_IDES_OPEN_FIX[1].ideName });
+
+ expect(secondIde).toBeInTheDocument();
+
+ await user.click(secondIde);
+
+ expect(openFixOrIssueInSonarLint).toHaveBeenCalledWith({
+ branchLike: {},
+ calledPort: MOCK_IDES_OPEN_FIX[1].port,
+ fix: FIX_DATA,
+ issueKey: MOCK_ISSUE_KEY,
+ projectKey: MOCK_PROJECT_KEY,
+ token: MOCK_TOKEN,
+ });
+});
+
+function renderComponentOpenIssueInIdeButton(
+ props: Partial<ComponentProps<typeof OpenFixInIde>> = {},
+) {
+ const mockedIssue = mockIssue(false, {
+ key: MOCK_ISSUE_KEY,
+ projectKey: MOCK_PROJECT_KEY,
+ });
+
+ function Wrapper() {
+ const queryClient = useQueryClient();
+ queryClient.setQueryData(['branches', 'mycomponent', 'details'], { branchLike: {} });
+ return <OpenFixInIde aiSuggestion={AI_SUGGESTION} issue={mockedIssue} {...props} />;
+ }
+
+ return renderComponent(<Wrapper />, '/?id=mycomponent', { currentUser: mockLoggedInUser() });
+}
themeColor,
} from 'design-system';
import * as React from 'react';
-import { getBranchLikeQuery, isBranch, isPullRequest } from '~sonar-aligned/helpers/branch-like';
+import { getBranchLikeQuery } from '~sonar-aligned/helpers/branch-like';
import { getComponentIssuesUrl } from '~sonar-aligned/helpers/urls';
import { ComponentQualifier } from '~sonar-aligned/types/component';
import { ComponentContext } from '../../../app/components/componentContext/ComponentContext';
linkToProject?: boolean;
loading?: boolean;
onExpand?: () => void;
+ secondaryActions?: React.ReactNode;
shouldShowOpenInIde?: boolean;
shouldShowViewAllIssues?: boolean;
sourceViewerFile: SourceViewerFile;
sourceViewerFile,
shouldShowOpenInIde = true,
shouldShowViewAllIssues = true,
+ secondaryActions,
} = props;
const { measures, path, project, projectName, q } = sourceViewerFile;
border-bottom: none;
`;
- const [branchName, pullRequestID] = React.useMemo(() => {
- if (isBranch(branchLike)) {
- return [branchLike.name, undefined];
- }
-
- if (isPullRequest(branchLike)) {
- return [branchLike.branch, branchLike.key];
- }
-
- return [undefined, undefined]; // should never end up here, but needed for consistent returns
- }, [branchLike]);
-
return (
<IssueSourceViewerStyle
aria-label={sourceViewerFile.path}
{!isProjectRoot && shouldShowOpenInIde && isLoggedIn(currentUser) && !isLoadingBranches && (
<IssueOpenInIdeButton
- branchName={branchName}
+ branchLike={branchLike}
issueKey={issueKey}
login={currentUser.login}
projectKey={project}
- pullRequestID={pullRequestID}
/>
)}
+ {secondaryActions && <div>{secondaryActions}</div>}
+
{!isProjectRoot && shouldShowViewAllIssues && measures.issues !== undefined && (
<div
className={classNames('sw-ml-4', {
SonarCodeColorizer,
themeColor,
} from 'design-system';
+import { OpenFixInIde } from '../../apps/issues/components/OpenFixInIde';
import { IssueSourceViewerHeader } from '../../apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader';
import { translate } from '../../helpers/l10n';
import { useComponentForSourceViewer } from '../../queries/component';
sourceViewerFile={sourceViewerFile}
shouldShowOpenInIde={false}
shouldShowViewAllIssues={false}
+ secondaryActions={<OpenFixInIde aiSuggestion={suggestion} issue={issue} />}
/>
)}
<SourceFileWrapper className="js-source-file sw-mb-4">
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { TokenType } from '../../types/token';
+import { NewUserToken, TokenType } from '../../types/token';
import { HttpStatus } from '../request';
import {
buildPortRange,
+ openFixOrIssueInSonarLint,
openHotspot,
- openIssue,
portIsValid,
probeSonarLintServers,
sendUserToken,
});
});
-describe('openIssue', () => {
+describe('open ide', () => {
it('should send the correct request to the IDE to open an issue', async () => {
let branchName: string | undefined = undefined;
let pullRequestID: string | undefined = undefined;
return Promise.resolve(resp);
});
- type OpenIssueParams = Parameters<typeof openIssue>[0];
+ type OpenIssueParams = Parameters<typeof openFixOrIssueInSonarLint>[0];
type PartialOpenIssueParams = Partial<OpenIssueParams>;
let params: PartialOpenIssueParams = {};
const testWith = async (args: PartialOpenIssueParams) => {
params = { ...params, ...args };
- const result = await openIssue(params as OpenIssueParams);
+ const result = await openFixOrIssueInSonarLint(params as OpenIssueParams);
expect(result).toBe(resp);
};
});
branchName = 'branch-1';
- await testWith({ branchName });
+ await testWith({ branchLike: { name: branchName, isMain: false, excludedFromPurge: false } });
pullRequestID = 'pr-1';
- await testWith({ pullRequestID });
+ await testWith({
+ branchLike: {
+ key: pullRequestID,
+ branch: branchName,
+ name: branchName,
+ base: 'foo',
+ target: 'bar',
+ title: 'test',
+ },
+ });
tokenName = 'token-name';
tokenValue = 'token-value';
- await testWith({ tokenName, tokenValue });
+ await testWith({ token: { token: tokenValue, name: tokenName } as NewUserToken });
});
});
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { omit } from 'lodash';
import { generateToken, getTokens } from '../api/user-tokens';
import { getHostUrl } from '../helpers/urls';
-import { Ide } from '../types/sonarlint';
+import { isBranch, isPullRequest } from '../sonar-aligned/helpers/branch-like';
+import { BranchLike } from '../types/branch-like';
+import { Fix, Ide } from '../types/sonarlint';
import { NewUserToken, TokenExpiration } from '../types/token';
import { UserBase } from '../types/users';
import { checkStatus, isSuccessStatus } from './request';
fetch(buildSonarLintEndpoint(p, '/status'))
.then((r) => r.json())
.then((json) => {
- const { description, ideName, needsToken } = json;
-
- return { description, ideName, needsToken, port: p } as Ide;
+ return { port: p, ...omit(json, 'p') };
})
.catch(() => undefined),
);
return generateToken({ expirationDate, login, name });
};
-export function openIssue({
- branchName,
+export function openFixOrIssueInSonarLint({
+ branchLike,
calledPort,
+ fix,
issueKey,
projectKey,
- pullRequestID,
- tokenName,
- tokenValue,
+ token,
}: {
- branchName?: string;
+ branchLike: BranchLike | undefined;
calledPort: number;
+ fix?: Fix;
issueKey: string;
projectKey: string;
- pullRequestID?: string;
- tokenName?: string;
- tokenValue?: string;
+ token?: NewUserToken;
}) {
- const showUrl = new URL(buildSonarLintEndpoint(calledPort, '/issues/show'));
+ const showUrl = new URL(
+ buildSonarLintEndpoint(calledPort, fix === undefined ? '/issues/show' : '/fix/show'),
+ );
showUrl.searchParams.set('server', getHostUrl());
showUrl.searchParams.set('project', projectKey);
showUrl.searchParams.set('issue', issueKey);
- if (branchName !== undefined) {
- showUrl.searchParams.set('branch', branchName);
+ if (isBranch(branchLike)) {
+ showUrl.searchParams.set('branch', branchLike.name);
}
- if (pullRequestID !== undefined) {
- showUrl.searchParams.set('pullRequest', pullRequestID);
+ if (isPullRequest(branchLike)) {
+ showUrl.searchParams.set('branch', branchLike.branch);
+ showUrl.searchParams.set('pullRequest', branchLike.key);
}
- if (tokenName !== undefined && tokenValue !== undefined) {
- showUrl.searchParams.set('tokenName', tokenName);
- showUrl.searchParams.set('tokenValue', tokenValue);
+ if (token !== undefined) {
+ showUrl.searchParams.set('tokenName', token.name);
+ showUrl.searchParams.set('tokenValue', token.token);
}
+ if (fix !== undefined) {
+ return fetch(showUrl.toString(), { method: 'POST', body: JSON.stringify(fix) }).then(
+ (response: Response) => checkStatus(response, true),
+ );
+ }
return fetch(showUrl.toString()).then((response: Response) => checkStatus(response, true));
}
},
);
-export function useComponentForSourceViewer(fileKey: string, branchLike?: BranchLike) {
+export function useComponentForSourceViewer(
+ fileKey: string,
+ branchLike?: BranchLike,
+ enabled = true,
+) {
return useQuery({
queryKey: ['component', 'source-viewer', fileKey, branchLike] as const,
queryFn: ({ queryKey: [_1, _2, fileKey, branchLike] }) =>
getComponentForSourceViewer({ component: fileKey, ...getBranchLikeQuery(branchLike) }),
staleTime: Infinity,
+ enabled,
});
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { useMutation } from '@tanstack/react-query';
+import { addGlobalErrorMessage, addGlobalSuccessMessage } from 'design-system/lib';
+import { useCurrentUser } from '../app/components/current-user/CurrentUserContext';
+import { translate } from '../helpers/l10n';
+import { generateSonarLintUserToken, openFixOrIssueInSonarLint } from '../helpers/sonarlint';
+import { BranchLike } from '../types/branch-like';
+import { Fix, Ide } from '../types/sonarlint';
+import { Issue } from '../types/types';
+import { isLoggedIn } from '../types/users';
+
+export function useOpenFixOrIssueInIdeMutation() {
+ const { currentUser } = useCurrentUser();
+ const login: string | undefined = isLoggedIn(currentUser) ? currentUser.login : undefined;
+
+ return useMutation({
+ mutationFn: async (data: {
+ branchLike: BranchLike | undefined;
+ fix?: Fix;
+ ide: Ide;
+ issue: Issue;
+ }) => {
+ const { ide, branchLike, issue, fix } = data;
+
+ const { key: issueKey, projectKey } = issue;
+
+ let token;
+ if (ide.needsToken && login !== undefined) {
+ token = await generateSonarLintUserToken({ ideName: ide.ideName, login });
+ }
+
+ return openFixOrIssueInSonarLint({
+ branchLike,
+ calledPort: ide.port,
+ issueKey,
+ projectKey,
+ token,
+ fix,
+ });
+ },
+ onSuccess: (_, arg) => {
+ if (arg.fix) {
+ addGlobalSuccessMessage(translate('fix_in_ide.report_success'));
+ } else {
+ addGlobalSuccessMessage(translate('open_in_ide.report_success'));
+ }
+ },
+ onError: (_, arg) => {
+ if (arg.fix) {
+ addGlobalErrorMessage(translate('fix_in_ide.report_error'));
+ } else {
+ addGlobalErrorMessage(translate('open_in_ide.report_error'));
+ }
+ },
+ });
+}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export interface Ide {
+ capabilities?: Capabilities;
description: string;
ideName: string;
needsToken?: boolean;
port: number;
}
+
+export interface Capabilities {
+ canOpenFixSuggestion: boolean;
+}
+
+export interface LineRange {
+ endLine: number;
+ startLine: number;
+}
+
+export interface Changes {
+ after: string;
+ before: string;
+ beforeLineRange: LineRange;
+}
+
+export interface EditFile {
+ changes: Changes[];
+ path: string;
+}
+
+export interface Fix {
+ explanation: string;
+ fileEdit: EditFile;
+ suggestionId: string;
+}
work_duration.about=~ {0}
+#------------------------------------------------------------------------------
+#
+# Open Fix in ide
+#
+#------------------------------------------------------------------------------
+view_fix_in_ide=View fix in IDE
+fix_in_ide.report_success=Success. Switch to your IDE to see the fix.
+fix_in_ide.report_error=Unable to open the fix in the IDE.
+unable_to_find_ide_with_fix.error=Unable to find IDE with capabilities to show fix suggestions
+
+
#------------------------------------------------------------------------------
#
# DAY PICKER