]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20023 Disable setting code type and severity when using bulk action
author7PH <benjamin.raymond@sonarsource.com>
Mon, 31 Jul 2023 16:50:44 +0000 (18:50 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 18 Aug 2023 20:02:47 +0000 (20:02 +0000)
12 files changed:
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-it.tsx
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
server/sonar-web/src/main/js/components/issue/components/DeprecatedFieldTooltip.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueSeverity.tsx
server/sonar-web/src/main/js/components/issue/components/IssueType.tsx
server/sonar-web/src/main/js/components/shared/CleanCodeAttributePill.tsx
server/sonar-web/src/main/js/components/shared/SoftwareImpactPill.tsx

index f337fbb193142968b62b07ef9036a1b1998a39f2..1e98e130101c2d95fe6dfae032af596d03a646a9 100644 (file)
@@ -172,11 +172,12 @@ export default class IssuesServiceMock {
   }
 
   handleBulkChangeIssues = (issueKeys: string[], query: RequestData) => {
-    //For now we only check for issue type change.
+    // For now we only check for issue type and status change.
     this.list
       .filter((i) => issueKeys.includes(i.issue.key))
       .forEach((data) => {
-        data.issue.type = query.set_type;
+        data.issue.type = query.set_type ?? data.issue.type;
+        data.issue.status = query.do_transition ?? data.issue.status;
       });
     return this.reply(undefined);
   };
index 8b5a8f1084b480d306e2310c00df2388d6b2f84b..3a8003b317a74330752b4b29f8008d4cc47bac86 100644 (file)
@@ -21,7 +21,6 @@
 import { act, screen, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import React from 'react';
-import selectEvent from 'react-select-event';
 import { TabKeys } from '../../../components/rules/RuleTabViewer';
 import { mockLoggedInUser } from '../../../helpers/testMocks';
 import { byRole } from '../../../helpers/testSelector';
@@ -259,11 +258,8 @@ describe('issues app', () => {
       expect(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.10' })).toHaveFocus();
       await user.click(screen.getByRole('checkbox', { name: 'issues.select_all_issues' }));
 
-      expect(
-        within(screen.getByRole('region', { name: 'Fix that' })).getByLabelText(
-          'issue.type.type_x_click_to_change.issue.type.CODE_SMELL'
-        )
-      ).toBeInTheDocument();
+      // Check that we bulk change the selected issue
+      const issueBoxFixThat = within(screen.getByRole('region', { name: 'Fix that' }));
 
       await user.click(
         screen.getByRole('checkbox', { name: 'issues.action_select.label.Fix that' })
@@ -274,16 +270,14 @@ describe('issues app', () => {
       await user.keyboard('New Comment');
       expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
 
-      await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_type' }), [
-        'issue.type.BUG',
-      ]);
+      await user.click(screen.getByRole('radio', { name: 'issue.transition.falsepositive' }));
       await user.click(screen.getByRole('button', { name: 'apply' }));
 
       expect(
-        await within(screen.getByRole('region', { name: 'Fix that' })).findByLabelText(
-          'issue.type.type_x_click_to_change.issue.type.BUG'
+        issueBoxFixThat.queryByLabelText(
+          'issue.transition.status_x_click_to_change.issue.status.falsepositive'
         )
-      ).toBeInTheDocument();
+      ).not.toBeInTheDocument();
     });
   });
 });
@@ -379,39 +373,6 @@ describe('issues item', () => {
     // Get a specific issue list item
     const listItem = within(await screen.findByRole('region', { name: 'Fix that' }));
 
-    // Change issue type
-    await act(async () => {
-      await user.click(
-        listItem.getByLabelText('issue.type.type_x_click_to_change.issue.type.CODE_SMELL')
-      );
-    });
-    expect(listItem.getByText('issue.type.BUG')).toBeInTheDocument();
-    expect(listItem.getByText('issue.type.VULNERABILITY')).toBeInTheDocument();
-
-    await act(async () => {
-      await user.click(listItem.getByText('issue.type.VULNERABILITY'));
-    });
-    expect(
-      listItem.getByLabelText('issue.type.type_x_click_to_change.issue.type.VULNERABILITY')
-    ).toBeInTheDocument();
-
-    // Change issue severity
-    expect(listItem.getByText('severity.MAJOR')).toBeInTheDocument();
-
-    await act(async () => {
-      await user.click(
-        listItem.getByLabelText('issue.severity.severity_x_click_to_change.severity.MAJOR')
-      );
-    });
-    expect(listItem.getByText('severity.MINOR')).toBeInTheDocument();
-    expect(listItem.getByText('severity.INFO')).toBeInTheDocument();
-    await act(async () => {
-      await user.click(listItem.getByText('severity.MINOR'));
-    });
-    expect(
-      listItem.getByLabelText('issue.severity.severity_x_click_to_change.severity.MINOR')
-    ).toBeInTheDocument();
-
     // Change issue status
     expect(listItem.getByText('issue.status.OPEN')).toBeInTheDocument();
 
@@ -444,7 +405,6 @@ describe('issues item', () => {
     ).not.toBeInTheDocument();
 
     // Assign issue to a different user
-
     await act(async () => {
       await user.click(
         listItem.getByRole('combobox', { name: 'issue.assign.unassigned_click_to_assign' })
@@ -550,21 +510,13 @@ describe('issues item', () => {
     await act(async () => {
       await user.click(await ui.issueItemAction5.find());
 
-      // open severity popup on key press 'i'
-
-      await user.keyboard('i');
-    });
-    expect(screen.getByText('severity.MINOR')).toBeInTheDocument();
-    expect(screen.getByText('severity.INFO')).toBeInTheDocument();
-
-    // open status popup on key press 'f'
-    await act(async () => {
+      // Open status popup on key press 'f'
       await user.keyboard('f');
     });
     expect(screen.getByText('issue.transition.confirm')).toBeInTheDocument();
     expect(screen.getByText('issue.transition.resolve')).toBeInTheDocument();
 
-    // open comment popup on key press 'c'
+    // Open comment popup on key press 'c'
     await act(async () => {
       await user.keyboard('c');
     });
@@ -573,19 +525,19 @@ describe('issues item', () => {
       await user.keyboard('{Escape}');
     });
 
-    // open tags popup on key press 't'
-
+    // Open tags popup on key press 't'
     await act(async () => {
       await user.keyboard('t');
     });
     expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
     expect(screen.getByText('android')).toBeInTheDocument();
     expect(screen.getByText('accessibility')).toBeInTheDocument();
-    // closing tags popup
+
+    // Close tags popup
     await act(async () => {
       await user.click(screen.getByText('issue.no_tag'));
 
-      // open assign popup on key press 'a'
+      // Open assign popup on key press 'a'
       await user.keyboard('a');
     });
     expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
@@ -692,14 +644,6 @@ describe('redirects', () => {
 
     expect(screen.getByText('/security_hotspots?assignedToMe=false')).toBeInTheDocument();
   });
-
-  // it('should filter out hotspots', () => {
-  //   renderProjectIssuesApp(
-  //     `project/issues?types=${IssueType.SecurityHotspot},${IssueType.CodeSmell}`
-  //   );
-
-  //   expect(ui.clearIssueTypeFacet.get()).toBeInTheDocument();
-  // });
 });
 
 describe('Activity', () => {
index b9ace10c84da003e9f8cf092688c21eba1b17a03..b91c1521ab8737415f14dc1bd10c533ba312a26a 100644 (file)
@@ -25,7 +25,6 @@ import {
   FormField,
   HelperHintIcon,
   Highlight,
-  InputSelect,
   LabelValueSelectOption,
   LightLabel,
   Modal,
@@ -38,14 +37,10 @@ import { FormattedMessage } from 'react-intl';
 import { SingleValue } from 'react-select';
 import { bulkChangeIssues, searchIssueTags } from '../../../api/issues';
 import FormattingTips from '../../../components/common/FormattingTips';
-import IssueSeverityIcon from '../../../components/icon-mappers/IssueSeverityIcon';
-import IssueTypeIcon from '../../../components/icon-mappers/IssueTypeIcon';
-import { SEVERITIES } from '../../../helpers/constants';
 import { throwGlobalError } from '../../../helpers/error';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { withBranchStatusRefresh } from '../../../queries/branch';
-import { IssueSeverity } from '../../../types/issues';
-import { Dict, Issue, IssueType, Paging } from '../../../types/types';
+import { Dict, Issue, Paging } from '../../../types/types';
 import AssigneeSelect from './AssigneeSelect';
 import TagsSelect from './TagsSelect';
 
@@ -162,15 +157,6 @@ export class BulkChangeModal extends React.PureComponent<Props, State> {
     this.setState({ comment: event.currentTarget.value });
   };
 
-  handleSelectFieldChange =
-    (field: 'severity' | 'type') => (data: LabelValueSelectOption<string> | null) => {
-      if (data) {
-        this.setState<keyof FormFields>({ [field]: data.value });
-      } else {
-        this.setState<keyof FormFields>({ [field]: undefined });
-      }
-    };
-
   handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
     event.preventDefault();
 
@@ -280,65 +266,6 @@ export class BulkChangeModal extends React.PureComponent<Props, State> {
     return this.renderField(field, 'issue.assign.formlink', affected, input);
   };
 
-  renderTypeField = () => {
-    const affected = this.state.issues.filter(hasAction('set_type')).length;
-    const field = InputField.type;
-
-    if (affected === 0) {
-      return null;
-    }
-
-    const types: IssueType[] = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
-    const options: LabelValueSelectOption<IssueType>[] = types.map((type) => ({
-      label: translate('issue.type', type),
-      value: type,
-      Icon: <IssueTypeIcon type={type} />,
-    }));
-
-    const input = (
-      <InputSelect
-        className="sw-w-abs-300"
-        inputId={`issues-bulk-change-${field}`}
-        isClearable
-        isSearchable={false}
-        onChange={this.handleSelectFieldChange('type')}
-        options={options}
-        size="full"
-      />
-    );
-
-    return this.renderField(field, 'issue.set_type', affected, input);
-  };
-
-  renderSeverityField = () => {
-    const affected = this.state.issues.filter(hasAction('set_severity')).length;
-    const field = InputField.severity;
-
-    if (affected === 0) {
-      return null;
-    }
-
-    const options: LabelValueSelectOption<IssueSeverity>[] = SEVERITIES.map((severity) => ({
-      label: translate('severity', severity),
-      value: severity,
-      Icon: <IssueSeverityIcon severity={severity} />,
-    }));
-
-    const input = (
-      <InputSelect
-        className="sw-w-abs-300"
-        inputId={`issues-bulk-change-${field}`}
-        isClearable
-        isSearchable={false}
-        onChange={this.handleSelectFieldChange('severity')}
-        options={options}
-        size="full"
-      />
-    );
-
-    return this.renderField(field, 'issue.set_severity', affected, input);
-  };
-
   renderTagsField = (
     field: InputField.addTags | InputField.removeTags,
     label: string,
@@ -464,8 +391,6 @@ export class BulkChangeModal extends React.PureComponent<Props, State> {
           )}
 
           {this.renderAssigneeField()}
-          {this.renderTypeField()}
-          {this.renderSeverityField()}
           {!needIssueSync && this.renderTagsField(InputField.addTags, 'issue.add_tags', true)}
 
           {!needIssueSync &&
index 1d59ba20401841107bafc6cbc3b993c64d8e5f9e..5cf5594f19b34da19ff94404850deed594025eea 100644 (file)
 import { act, screen } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import * as React from 'react';
-import selectEvent from 'react-select-event';
 import { bulkChangeIssues } from '../../../../api/issues';
 import CurrentUserContextProvider from '../../../../app/components/current-user/CurrentUserContextProvider';
-import { SEVERITIES } from '../../../../helpers/constants';
 import { mockIssue, mockLoggedInUser } from '../../../../helpers/testMocks';
 import { renderComponent } from '../../../../helpers/testReactTestingUtils';
 import { ComponentPropsType } from '../../../../helpers/testUtils';
-import { IssueType } from '../../../../types/issues';
 import { Issue } from '../../../../types/types';
 import { CurrentUser } from '../../../../types/users';
 import BulkChangeModal, { MAX_PAGE_SIZE } from '../BulkChangeModal';
@@ -64,15 +61,6 @@ it('should display warning when too many issues are passed', async () => {
   expect(await screen.findByText('issue_bulk_change.max_issues_reached')).toBeInTheDocument();
 });
 
-it.each([
-  ['type', 'set_type'],
-  ['severity', 'set_severity'],
-])('should render select for %s', async (_field, action) => {
-  renderBulkChangeModal([mockIssue(false, { actions: [action] })]);
-
-  expect(await screen.findByText('issue.' + action)).toBeInTheDocument();
-});
-
 it('should render tags correctly', async () => {
   renderBulkChangeModal([mockIssue(false, { actions: ['set_tags'] })]);
 
@@ -91,7 +79,7 @@ it('should render transitions correctly', async () => {
 
 it('should disable the submit button unless some change is configured', async () => {
   const user = userEvent.setup();
-  renderBulkChangeModal([mockIssue(false, { actions: ['set_severity', 'comment'] })]);
+  renderBulkChangeModal([mockIssue(false, { actions: ['set_tags', 'comment'] })]);
 
   // Apply button should be disabled
   expect(await screen.findByRole('button', { name: 'apply' })).toBeDisabled();
@@ -100,10 +88,12 @@ it('should disable the submit button unless some change is configured', async ()
   await user.type(screen.getByRole('textbox', { name: /issue.comment.formlink/ }), 'some comment');
   expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
 
-  // Select a severity
-  await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
-    `severity.${SEVERITIES[0]}`,
-  ]);
+  // Add a tag
+  await act(async () => {
+    await user.click(screen.getByRole('combobox', { name: 'issue.add_tags' }));
+    await user.click(screen.getByText('tag1'));
+    await user.click(screen.getByText('tag2'));
+  });
 
   // Apply button should be enabled now
   expect(screen.getByRole('button', { name: 'apply' })).toBeEnabled();
@@ -155,21 +145,6 @@ it('should properly submit', async () => {
     await user.click(screen.getByText('tag2'));
   });
 
-  // Select a type
-  await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_type' }), [
-    `issue.type.CODE_SMELL`,
-  ]);
-
-  // Select a severity
-  await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
-    `severity.${SEVERITIES[0]}`,
-  ]);
-
-  // Severity
-  await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
-    `severity.${SEVERITIES[0]}`,
-  ]);
-
   // Comment
   await user.type(screen.getByRole('textbox', { name: /issue.comment.formlink/ }), 'some comment');
 
@@ -188,8 +163,6 @@ it('should properly submit', async () => {
     comment: 'some comment',
     do_transition: 'Transition2',
     sendNotifications: true,
-    set_severity: 'BLOCKER',
-    set_type: IssueType.CodeSmell,
   });
 });
 
index ce8fd1957da698ed445abd8d87fe2df735fa8127..00c831f6a0472e87bab7652d5f3bcad2ccbba65d 100644 (file)
@@ -27,7 +27,6 @@ import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
 import { HttpStatus } from '../../../helpers/request';
 import { mockIssue } from '../../../helpers/testMocks';
 import { renderComponent } from '../../../helpers/testReactTestingUtils';
-import { byText } from '../../../helpers/testSelector';
 import SourceViewer, { Props } from '../SourceViewer';
 import loadIssues from '../helpers/loadIssues';
 
@@ -51,11 +50,6 @@ jest.mock('../helpers/lines', () => {
   };
 });
 
-const ui = {
-  codeSmellTypeButton: byText('issue.type.CODE_SMELL'),
-  minorSeverityButton: byText(/severity.MINOR/),
-};
-
 const componentsHandler = new ComponentsServiceMock();
 const issuesHandler = new IssuesServiceMock();
 const message = 'First Issue';
@@ -140,7 +134,7 @@ it('should show issue on empty file', async () => {
 it('should be able to interact with issue action', async () => {
   jest.mocked(loadIssues).mockResolvedValueOnce([
     mockIssue(false, {
-      actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
+      actions: ['set_tags', 'comment', 'assign'],
       key: 'issue1',
       message,
       line: 1,
@@ -151,35 +145,15 @@ it('should be able to interact with issue action', async () => {
   const user = userEvent.setup();
   renderSourceViewer();
 
-  //Open Issue type
-  await user.click(
-    await screen.findByLabelText('issue.type.type_x_click_to_change.issue.type.BUG')
-  );
-
-  expect(ui.codeSmellTypeButton.get()).toBeInTheDocument();
-
-  // Open severity
-  await user.click(
-    await screen.findByLabelText('issue.severity.severity_x_click_to_change.severity.MAJOR')
-  );
-
-  expect(ui.minorSeverityButton.get()).toBeInTheDocument();
-
-  // Close
-  await user.keyboard('{Escape}');
-  expect(ui.minorSeverityButton.query()).not.toBeInTheDocument();
-
-  // Change the severity
-  await user.click(
-    await screen.findByLabelText('issue.severity.severity_x_click_to_change.severity.MAJOR')
-  );
-
-  expect(ui.minorSeverityButton.get()).toBeInTheDocument();
-  await user.click(ui.minorSeverityButton.get());
-
-  expect(
-    screen.getByLabelText('issue.severity.severity_x_click_to_change.severity.MINOR')
-  ).toBeInTheDocument();
+  // Assign issue to a different user
+  await act(async () => {
+    await user.click(
+      await screen.findByRole('combobox', { name: 'issue.assign.unassigned_click_to_assign' })
+    );
+    await user.click(screen.getByLabelText('search.search_for_users'));
+    await user.keyboard('luke');
+  });
+  expect(screen.getByText('Skywalker')).toBeInTheDocument();
 });
 
 it('should load line when looking around unloaded line', async () => {
index b1116843b621489858b2c16fd365f7220a2acef0..50bce2d40af8c1f015ca1efb14ef8f3ff5b46ba3 100644 (file)
@@ -104,32 +104,6 @@ describe('rendering', () => {
 });
 
 describe('updating', () => {
-  it('should allow updating the type', async () => {
-    const { ui } = getPageObject();
-    const issue = mockRawIssue(false, {
-      type: IssueType.Bug,
-      actions: [IssueActions.SetType],
-    });
-    issuesHandler.setIssueList([{ issue, snippets: {} }]);
-    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'type') }) });
-
-    await ui.updateType(IssueType.Bug, IssueType.CodeSmell);
-    expect(ui.updateTypeBtn(IssueType.CodeSmell).get()).toBeInTheDocument();
-  });
-
-  it('should allow updating the severity', async () => {
-    const { ui } = getPageObject();
-    const issue = mockRawIssue(false, {
-      severity: IssueSeverity.Blocker,
-      actions: [IssueActions.SetSeverity],
-    });
-    issuesHandler.setIssueList([{ issue, snippets: {} }]);
-    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'severity') }) });
-
-    await ui.updateSeverity(IssueSeverity.Blocker, IssueSeverity.Minor);
-    expect(ui.updateSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
-  });
-
   it('should allow updating the status', async () => {
     const { ui } = getPageObject();
     const issue = mockRawIssue(false, {
@@ -199,10 +173,6 @@ it('should correctly handle keyboard shortcuts', async () => {
   expect(ui.setAssigneeBtn(/Organa/).get()).toBeInTheDocument();
   await ui.pressDismissShortcut();
 
-  await ui.pressSeverityShortcut();
-  expect(ui.setSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
-  await ui.pressDismissShortcut();
-
   await ui.pressCommentShortcut();
   expect(ui.commentTextInput.get()).toBeInTheDocument();
   await ui.pressDismissShortcut();
diff --git a/server/sonar-web/src/main/js/components/issue/components/DeprecatedFieldTooltip.tsx b/server/sonar-web/src/main/js/components/issue/components/DeprecatedFieldTooltip.tsx
new file mode 100644 (file)
index 0000000..1dcb838
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { Link } from 'design-system';
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { translate } from '../../../helpers/l10n';
+
+export interface DeprecatedTooltipProps {
+  docUrl: string;
+  field: 'type' | 'severity';
+}
+
+export function DeprecatedFieldTooltip({ field, docUrl }: DeprecatedTooltipProps) {
+  return (
+    <>
+      <p className="sw-mb-4">{translate('issue', field, 'deprecation.title')}</p>
+      <p>{translate('issue', field, 'deprecation.filter_by')}</p>
+      <ul className="sw-list-disc sw-ml-6">
+        <li>{translate('issue.clean_code_attributes')}</li>
+        <li>{translate('issue.software_qualities')}</li>
+      </ul>
+      <hr className="sw-w-full sw-mx-0 sw-my-4" />
+      <FormattedMessage
+        defaultMessage={translate('learn_more_x')}
+        id="learn_more_x"
+        values={{
+          link: (
+            <Link isExternal to={docUrl}>
+              {translate('issue', field, 'deprecation.documentation')}
+            </Link>
+          ),
+        }}
+      />
+    </>
+  );
+}
index 6665b3e6639595d6ad805d2f67fadbec2b26ab35..77fa8032852ec6b6056852ad868b48d7eda49649 100644 (file)
@@ -24,19 +24,13 @@ import { Badge, CommentIcon, SeparatorCircleIcon, themeColor } from 'design-syst
 import * as React from 'react';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { isDefined } from '../../../helpers/types';
-import {
-  IssueActions,
-  IssueResolution,
-  IssueResponse,
-  IssueType as IssueTypeEnum,
-} from '../../../types/issues';
+import { IssueActions, IssueResolution, IssueType as IssueTypeEnum } from '../../../types/issues';
 import { RuleStatus } from '../../../types/rules';
-import { Issue, RawQuery } from '../../../types/types';
+import { Issue } from '../../../types/types';
 import Tooltip from '../../controls/Tooltip';
 import DateFromNow from '../../intl/DateFromNow';
 import SoftwareImpactPill from '../../shared/SoftwareImpactPill';
 import { WorkspaceContext } from '../../workspace/context';
-import { updateIssue } from '../actions';
 import IssueAssign from './IssueAssign';
 import IssueBadges from './IssueBadges';
 import IssueCommentAction from './IssueCommentAction';
@@ -77,20 +71,6 @@ export default function IssueActionsBar(props: Props) {
     commentPlaceholder: '',
   });
 
-  const setIssueProperty = (
-    property: keyof Issue,
-    popup: string,
-    apiCall: (query: RawQuery) => Promise<IssueResponse>,
-    value: string
-  ) => {
-    if (issue[property] !== value) {
-      const newIssue = { ...issue, [property]: value };
-      updateIssue(onChange, apiCall({ issue: issue.key, [property]: value }), issue, newIssue);
-    }
-
-    togglePopup(popup, false);
-  };
-
   const toggleComment = (open: boolean, placeholder = '', autoTriggered = false) => {
     setCommentState({
       commentPlaceholder: placeholder,
@@ -119,8 +99,6 @@ export default function IssueActionsBar(props: Props) {
 
   const canAssign = issue.actions.includes(IssueActions.Assign);
   const canComment = issue.actions.includes(IssueActions.Comment);
-  const canSetSeverity = issue.actions.includes(IssueActions.SetSeverity);
-  const canSetType = issue.actions.includes(IssueActions.SetType);
   const hasTransitions = issue.transitions.length > 0;
   const hasComments = !!issue.comments?.length;
 
@@ -147,6 +125,16 @@ export default function IssueActionsBar(props: Props) {
           />
         </li>
 
+        <li>
+          <IssueAssign
+            isOpen={currentPopup === 'assign'}
+            togglePopup={togglePopup}
+            canAssign={canAssign}
+            issue={issue}
+            onAssign={onAssign}
+          />
+        </li>
+
         <li className="sw-flex sw-gap-3">
           {issue.impacts.map(({ severity, softwareQuality }, index) => (
             <SoftwareImpactPill
@@ -159,27 +147,11 @@ export default function IssueActionsBar(props: Props) {
         </li>
 
         <li>
-          <IssueType canSetType={canSetType} issue={issue} setIssueProperty={setIssueProperty} />
+          <IssueType issue={issue} />
         </li>
 
         <li>
-          <IssueSeverity
-            isOpen={currentPopup === 'set-severity'}
-            togglePopup={togglePopup}
-            canSetSeverity={canSetSeverity}
-            issue={issue}
-            setIssueProperty={setIssueProperty}
-          />
-        </li>
-
-        <li>
-          <IssueAssign
-            isOpen={currentPopup === 'assign'}
-            togglePopup={togglePopup}
-            canAssign={canAssign}
-            issue={issue}
-            onAssign={onAssign}
-          />
+          <IssueSeverity issue={issue} />
         </li>
       </ul>
       {canComment && (
index 1c80407309b9093856059ee08a3840cd0e92afe2..43712bc000491fc502273a963d4583fa255c1f30 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { DiscreetSelect } from 'design-system';
+import { DisabledText, Tooltip } from 'design-system';
 import * as React from 'react';
-import { setIssueSeverity } from '../../../api/issues';
-import { SEVERITIES } from '../../../helpers/constants';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { IssueResponse, IssueSeverity as IssueSeverityType } from '../../../types/issues';
-import { Issue, RawQuery } from '../../../types/types';
+import { useDocUrl } from '../../../helpers/docs';
+import { translate } from '../../../helpers/l10n';
+import { IssueSeverity as IssueSeverityType } from '../../../types/issues';
+import { Issue } from '../../../types/types';
 import IssueSeverityIcon from '../../icon-mappers/IssueSeverityIcon';
+import { DeprecatedFieldTooltip } from './DeprecatedFieldTooltip';
 
 interface Props {
-  canSetSeverity: boolean;
-  isOpen: boolean;
   issue: Pick<Issue, 'severity'>;
-  togglePopup: (popup: string, show?: boolean) => void;
-  setIssueProperty: (
-    property: keyof Issue,
-    popup: string,
-    apiCall: (query: RawQuery) => Promise<IssueResponse>,
-    value: string
-  ) => void;
 }
 
-export default class IssueSeverity extends React.PureComponent<Props> {
-  setSeverity = ({ value }: { value: string }) => {
-    this.props.setIssueProperty('severity', 'set-severity', setIssueSeverity, value);
-    this.toggleSetSeverity(false);
-  };
+export default function IssueSeverity({ issue }: Props) {
+  const docUrl = useDocUrl('/');
 
-  toggleSetSeverity = (open: boolean) => {
-    this.props.togglePopup('set-severity', open);
-  };
-
-  handleClose = () => {
-    this.toggleSetSeverity(false);
-  };
-
-  render() {
-    const { issue } = this.props;
-
-    const typesOptions = SEVERITIES.map((severity) => ({
-      label: translate('severity', severity),
-      value: severity,
-      Icon: <IssueSeverityIcon severity={severity} aria-hidden />,
-    }));
-
-    if (this.props.canSetSeverity) {
-      return (
-        <DiscreetSelect
-          aria-label={translateWithParameters(
-            'issue.severity.severity_x_click_to_change',
-            translate('severity', issue.severity)
-          )}
-          menuIsOpen={this.props.isOpen && this.props.canSetSeverity}
-          className="it__issue-severity"
-          options={typesOptions}
-          onMenuClose={this.handleClose}
-          onMenuOpen={() => this.toggleSetSeverity(true)}
-          setValue={this.setSeverity}
-          value={issue.severity}
+  return (
+    <Tooltip overlay={<DeprecatedFieldTooltip field="type" docUrl={docUrl} />}>
+      <DisabledText className="sw-flex sw-items-center sw-gap-1 sw-cursor-not-allowed">
+        <IssueSeverityIcon
+          fill="iconSeverityDisabled"
+          severity={issue.severity as IssueSeverityType}
+          aria-hidden
         />
-      );
-    }
-
-    return (
-      <span className="sw-flex sw-items-center sw-gap-1">
-        <IssueSeverityIcon severity={issue.severity as IssueSeverityType} aria-hidden />
-
         {translate('severity', issue.severity)}
-      </span>
-    );
-  }
+      </DisabledText>
+    </Tooltip>
+  );
 }
index a0be2494e77426b827a348a82fc79b6566b96d51..410d3a4cf027a0b2b6791c71df53095c9cc5818c 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 
-import { DiscreetSelect } from 'design-system';
+import { DisabledText, Tooltip } from 'design-system';
 import * as React from 'react';
-import { setIssueType } from '../../../api/issues';
-import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { IssueResponse } from '../../../types/issues';
-import { Issue, RawQuery } from '../../../types/types';
+import { useDocUrl } from '../../../helpers/docs';
+import { translate } from '../../../helpers/l10n';
+import { Issue } from '../../../types/types';
 import IssueTypeIcon from '../../icon-mappers/IssueTypeIcon';
+import { DeprecatedFieldTooltip } from './DeprecatedFieldTooltip';
 
 interface Props {
-  canSetType: boolean;
   issue: Pick<Issue, 'type'>;
-  setIssueProperty: (
-    property: keyof Issue,
-    popup: string,
-    apiCall: (query: RawQuery) => Promise<IssueResponse>,
-    value: string
-  ) => void;
 }
 
-export default class IssueType extends React.PureComponent<Props> {
-  setType = ({ value }: { value: string }) => {
-    this.props.setIssueProperty('type', 'set-type', setIssueType, value);
-  };
+export default function IssueType({ issue }: Props) {
+  const docUrl = useDocUrl('/');
 
-  render() {
-    const { issue } = this.props;
-    const TYPES = ['BUG', 'VULNERABILITY', 'CODE_SMELL'];
-
-    const typesOptions = TYPES.map((type) => ({
-      label: translate('issue.type', type),
-      value: type,
-      Icon: <IssueTypeIcon type={type} />,
-    }));
-
-    if (this.props.canSetType) {
-      return (
-        <DiscreetSelect
-          aria-label={translateWithParameters(
-            'issue.type.type_x_click_to_change',
-            translate('issue.type', issue.type)
-          )}
-          className="it__issue-type"
-          options={typesOptions}
-          setValue={this.setType}
-          value={issue.type}
-        />
-      );
-    }
-
-    return (
-      <span className="sw-flex sw-items-center sw-gap-1">
-        <IssueTypeIcon type={issue.type} />
+  return (
+    <Tooltip overlay={<DeprecatedFieldTooltip field="type" docUrl={docUrl} />}>
+      <DisabledText className="sw-flex sw-items-center sw-gap-1 sw-cursor-not-allowed">
+        <IssueTypeIcon fill="iconTypeDisabled" type={issue.type} aria-hidden />
         {translate('issue.type', issue.type)}
-      </span>
-    );
-  }
+      </DisabledText>
+    </Tooltip>
+  );
 }
index 838379ddd18a4e1f499120ca228a6bf3c7eeb47b..a652f2e8551e6075ac43bec17bcb2bc7ed6b01d5 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 classNames from 'classnames';
 import { Link, Pill } from 'design-system';
 import React from 'react';
index 6746200c6784df8b1bb3b65a802b9ff81bfd65e0..f011bf3d35d598a93a10182bd16da37aa53feef1 100644 (file)
@@ -1,3 +1,22 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 classNames from 'classnames';
 import { Link, Pill } from 'design-system';
 import React from 'react';