]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-23300 Add popover to temporal issue filters
authorstanislavh <stanislav.honcharov@sonarsource.com>
Thu, 31 Oct 2024 08:40:03 +0000 (09:40 +0100)
committersonartech <sonartech@sonarsource.com>
Fri, 1 Nov 2024 20:02:47 +0000 (20:02 +0000)
server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/issues/sidebar/SoftwareQualityFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/TypeFacet.tsx
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
server/sonar-web/src/main/js/components/facets/SeverityFacet.tsx
server/sonar-web/src/main/js/components/facets/StandardSeverityFacet.tsx
server/sonar-web/src/main/js/design-system/components/FacetBox.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx b/server/sonar-web/src/main/js/apps/issues/sidebar/QGMetricsMismatchHelp.tsx
new file mode 100644 (file)
index 0000000..f2e99fd
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * 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, IconQuestionMark, Popover } from '@sonarsource/echoes-react';
+import { useIntl } from 'react-intl';
+import DocumentationLink from '../../../components/common/DocumentationLink';
+import { DocLink } from '../../../helpers/doc-links';
+import { useStandardExperienceMode } from '../../../queries/settings';
+
+export default function QGMetricsMismatchHelp() {
+  const intl = useIntl();
+  const { data: isStandardMode } = useStandardExperienceMode();
+  return (
+    <Popover
+      title={intl.formatMessage({ id: 'issues.qg_mismatch.title' })}
+      description={intl.formatMessage({ id: 'issues.qg_mismatch.description' }, { isStandardMode })}
+      footer={
+        <DocumentationLink standalone to={DocLink.QualityGates}>
+          {intl.formatMessage({ id: 'issues.qg_mismatch.link' })}
+        </DocumentationLink>
+      }
+    >
+      <Button
+        className="sw-p-0 sw-h-fit sw-min-h-fit"
+        aria-label={intl.formatMessage({ id: 'help' })}
+        variety={ButtonVariety.DefaultGhost}
+      >
+        <IconQuestionMark />
+      </Button>
+    </Popover>
+  );
+}
index 34a1edbf1861e654e29302bd6c813578e6826051..310729c94c97f292675342e3ebf36fde8982ec4f 100644 (file)
@@ -20,6 +20,7 @@
 
 import { SOFTWARE_QUALITIES } from '../../../helpers/constants';
 import { SoftwareQuality } from '../../../types/clean-code-taxonomy';
+import QGMetricsMismatchHelp from './QGMetricsMismatchHelp';
 import { CommonProps, SimpleListStyleFacet } from './SimpleListStyleFacet';
 
 interface Props extends CommonProps {
@@ -35,6 +36,7 @@ export function SoftwareQualityFacet(props: Props) {
       itemNamePrefix="software_quality"
       listItems={SOFTWARE_QUALITIES}
       selectedItems={qualities}
+      help={Boolean(props.secondLine) && <QGMetricsMismatchHelp />}
       {...rest}
     />
   );
index a96858bd4bc7898667c0cbce185e268d5571ba8f..a0ed6e0403564bfea0e830d296499e4dd4c1888c 100644 (file)
@@ -28,6 +28,7 @@ import { Dict } from '../../../types/types';
 import { Query, formatFacetStat } from '../utils';
 import { FacetItemsList } from './FacetItemsList';
 import { MultipleSelectionHint } from './MultipleSelectionHint';
+import QGMetricsMismatchHelp from './QGMetricsMismatchHelp';
 
 interface Props {
   fetching: boolean;
@@ -128,6 +129,7 @@ export class TypeFacet extends React.PureComponent<Props> {
         onClear={this.handleClear}
         onClick={this.handleHeaderClick}
         open={open}
+        help={Boolean(secondLine) && <QGMetricsMismatchHelp />}
         secondLine={secondLine}
       >
         <FacetItemsList labelledby={typeFacetHeaderId}>
index 57d9bd85aa2969e50d0e0db4d31fcb39982c6349..730b7b8f42d67cc7f38d2dfa47c38f04b192690d 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 import { screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import { ComponentQualifier } from '~sonar-aligned/types/component';
 import SettingsServiceMock from '../../../../api/mocks/SettingsServiceMock';
 import { mockComponent } from '../../../../helpers/mocks/component';
@@ -78,7 +79,8 @@ describe('MQR mode', () => {
     ]);
   });
 
-  it('should show show mqr filters if they exist in query', async () => {
+  it('should show standard filters if they exist in query', async () => {
+    const user = userEvent.setup();
     let component = renderSidebar({
       query: mockQuery({ types: [IssueType.CodeSmell] }),
     });
@@ -93,6 +95,11 @@ describe('MQR mode', () => {
         .byText('issues.facet.second_line.mode.standard')
         .get(),
     ).toBeInTheDocument();
+    // help icon
+    expect(byRole('button', { name: 'help' }).get()).toBeInTheDocument();
+    await user.click(byRole('button', { name: 'help' }).get());
+    expect(screen.getByText('issues.qg_mismatch.title')).toBeInTheDocument();
+
     expect(
       screen.queryByRole('button', { name: 'issues.facet.severities' }),
     ).not.toBeInTheDocument();
@@ -237,6 +244,7 @@ describe('Standard mode', () => {
   });
 
   it('should show show mqr filters if they exist in query', async () => {
+    const user = userEvent.setup();
     let component = renderSidebar({
       query: mockQuery({ impactSeverities: [SoftwareImpactSeverity.Blocker] }),
     });
@@ -251,6 +259,12 @@ describe('Standard mode', () => {
         .byText('issues.facet.second_line.mode.mqr')
         .get(),
     ).toBeInTheDocument();
+
+    // help icon
+    expect(byRole('button', { name: 'help' }).get()).toBeInTheDocument();
+    await user.click(byRole('button', { name: 'help' }).get());
+    expect(screen.getByText('issues.qg_mismatch.title')).toBeInTheDocument();
+
     expect(
       screen.queryByRole('button', { name: 'issues.facet.impactSoftwareQualities' }),
     ).not.toBeInTheDocument();
index 1ec8d6223e3bc7108d8a11c1ff6074dc6fa7d7f7..23784daa404332f739b03e21ce845ad02a961b20 100644 (file)
@@ -22,6 +22,7 @@ import { Popover } from '@sonarsource/echoes-react';
 import * as React from 'react';
 import { useIntl } from 'react-intl';
 import { BareButton, HelperHintIcon } from '~design-system';
+import QGMetricsMismatchHelp from '../../apps/issues/sidebar/QGMetricsMismatchHelp';
 import { IMPACT_SEVERITIES } from '../../helpers/constants';
 import { DocLink } from '../../helpers/doc-links';
 import { translate } from '../../helpers/l10n';
@@ -54,24 +55,30 @@ export default function SeverityFacet(props: Readonly<BasicProps>) {
       renderName={renderName}
       renderTextName={renderTextName}
       help={
-        <Popover
-          title={intl.formatMessage({ id: 'severity_impact.levels' })}
-          description={
-            <>
-              <p>{intl.formatMessage({ id: 'severity_impact.help.line1' })}</p>
-              <p className="sw-mt-2">{intl.formatMessage({ id: 'severity_impact.help.line2' })}</p>
-            </>
-          }
-          footer={
-            <DocumentationLink to={DocLink.CleanCodeIntroduction}>
-              {intl.formatMessage({ id: 'learn_more' })}
-            </DocumentationLink>
-          }
-        >
-          <BareButton aria-label={intl.formatMessage({ id: 'more_information' })}>
-            <HelperHintIcon />
-          </BareButton>
-        </Popover>
+        props.secondLine ? (
+          <QGMetricsMismatchHelp />
+        ) : (
+          <Popover
+            title={intl.formatMessage({ id: 'severity_impact.levels' })}
+            description={
+              <>
+                <p>{intl.formatMessage({ id: 'severity_impact.help.line1' })}</p>
+                <p className="sw-mt-2">
+                  {intl.formatMessage({ id: 'severity_impact.help.line2' })}
+                </p>
+              </>
+            }
+            footer={
+              <DocumentationLink to={DocLink.CleanCodeIntroduction}>
+                {intl.formatMessage({ id: 'learn_more' })}
+              </DocumentationLink>
+            }
+          >
+            <BareButton aria-label={intl.formatMessage({ id: 'more_information' })}>
+              <HelperHintIcon />
+            </BareButton>
+          </Popover>
+        )
       }
     />
   );
index b667875a350559124fee70a21b8873be3bcc56bd..ce8801954ea4c572c9e6b6934741f99989edde6a 100644 (file)
@@ -19,6 +19,7 @@
  */
 
 import * as React from 'react';
+import QGMetricsMismatchHelp from '../../apps/issues/sidebar/QGMetricsMismatchHelp';
 import { SEVERITIES } from '../../helpers/constants';
 import { translate } from '../../helpers/l10n';
 import SoftwareImpactSeverityIcon from '../icon-mappers/SoftwareImpactSeverityIcon';
@@ -45,6 +46,7 @@ export default function StandardSeverityFacet(
   return (
     <Facet
       {...props}
+      help={Boolean(props.secondLine) && <QGMetricsMismatchHelp />}
       options={SEVERITIES}
       property="severities"
       renderName={renderName}
index bac0c8b20db4fdc0b2f95140f2361965e419a416..c93ac18933fcbab836c046f728c0ff4b11bbea69 100644 (file)
@@ -94,45 +94,47 @@ export function FacetBox(props: FacetBoxProps) {
       inner={inner}
     >
       <Header>
-        <TitleWithHelp>
-          <ChevronAndTitle
-            aria-controls={`${id}-panel`}
-            aria-disabled={!expandable}
-            aria-expanded={open}
-            aria-label={ariaLabel ?? name}
-            expandable={expandable}
-            id={`${id}-header`}
-            onClick={() => {
-              if (!disabled) {
-                onClick?.(!open);
-              }
-            }}
-          >
-            {expandable && <OpenCloseIndicator aria-hidden open={open} />}
-
-            {disabled ? (
-              <Tooltip content={disabledHelper}>
-                <HeaderTitle
-                  aria-disabled
-                  aria-label={`${name}, ${disabledHelper ?? ''}`}
-                  disabled={disabled}
-                >
-                  {name}
-                </HeaderTitle>
-              </Tooltip>
-            ) : (
-              <div>
-                <HeaderTitle>{name}</HeaderTitle>
-                {secondLine !== undefined && (
-                  <Text as="div" isSubdued>
-                    {secondLine}
-                  </Text>
-                )}
-              </div>
-            )}
-          </ChevronAndTitle>
+        <div className="sw-flex sw-items-center">
+          <TitleWithHelp>
+            <ChevronAndTitle
+              aria-controls={`${id}-panel`}
+              aria-disabled={!expandable}
+              aria-expanded={open}
+              aria-label={ariaLabel ?? name}
+              expandable={expandable}
+              id={`${id}-header`}
+              onClick={() => {
+                if (!disabled) {
+                  onClick?.(!open);
+                }
+              }}
+            >
+              {expandable && <OpenCloseIndicator aria-hidden open={open} />}
+
+              {disabled ? (
+                <Tooltip content={disabledHelper}>
+                  <HeaderTitle
+                    aria-disabled
+                    aria-label={`${name}, ${disabledHelper ?? ''}`}
+                    disabled={disabled}
+                  >
+                    {name}
+                  </HeaderTitle>
+                </Tooltip>
+              ) : (
+                <div>
+                  <HeaderTitle>{name}</HeaderTitle>
+                  {secondLine !== undefined && (
+                    <Text as="div" isSubdued>
+                      {secondLine}
+                    </Text>
+                  )}
+                </div>
+              )}
+            </ChevronAndTitle>
+          </TitleWithHelp>
           {help && <span className="sw-ml-1">{help}</span>}
-        </TitleWithHelp>
+        </div>
 
         {<Spinner loading={loading} />}
 
index db8dcea6d67a7f5a52fd9be01bd21172f190ce19..6613cc625e4fbb0834517b1dfc7e47e7e6183461 100644 (file)
@@ -1160,7 +1160,9 @@ issues.fixed_issues.description=List of issues that will be fixed by {pullReques
 issues.x_more_locations=+ {0} more locations
 issues.not_all_issue_show=Not all issues are included
 issues.not_all_issue_show_why=You do not have access to all projects in this portfolio
-
+issues.qg_mismatch.title=Quality Gate metrics mismatch
+issues.qg_mismatch.description=Your quality gate contains conditions from {isStandardMode, select, true {Multi-Quality Rule Mode} other {Standard Expirience}}. While your instance remains in the {isStandardMode, select, true {Standard Experience} other {Multi-Quality Rule Mode}} some additional filters will be visible for these {isStandardMode, select, true {MQR} other {Standard Expirience}} metrics.
+issues.qg_mismatch.link=Lear more about both modes in documentation
 issues.open_in_ide.success=Success. Switch to your IDE to see the issue.
 issues.open_in_ide.failure=Unable to open the issue in the IDE. Please check the {link}.