]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20337 Validation fixes for QG
authorKevin Silva <kevin.silva@sonarsource.com>
Mon, 18 Sep 2023 09:41:23 +0000 (11:41 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 19 Sep 2023 20:02:46 +0000 (20:02 +0000)
14 files changed:
server/sonar-web/design-system/src/components/avatar/GenericAvatar.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/App.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/CaycConditionsListItem.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionReviewAndUpdateModal.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/ConditionValueDescription.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/CreateQualityGateForm.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Details.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/List.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/Projects.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsAddModalRenderer.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/QualityGatePermissionsRenderer.tsx
server/sonar-web/src/main/js/components/controls/SelectListListContainer.tsx

index 38f4fbef6d915d681ee6bcec4d323c6959c0816e..2590ccbefdbb347975c5e2349605d7e0982031ee 100644 (file)
@@ -32,7 +32,12 @@ export interface GenericAvatarProps {
   size?: Size;
 }
 
-export function GenericAvatar({ className, Icon, name, size = 'sm' }: GenericAvatarProps) {
+export function GenericAvatar({
+  className,
+  Icon,
+  name,
+  size = 'sm',
+}: Readonly<GenericAvatarProps>) {
   const theme = useTheme();
   const text = name.length > 0 ? name[0].toUpperCase() : '';
 
index d2781723d6af48b79626d7f649739b005f0c4fe4..4eaeb02b60f9e24627e48a9a8e1fdc4d542fac50 100644 (file)
@@ -23,6 +23,8 @@ import {
   LAYOUT_FOOTER_HEIGHT,
   LAYOUT_GLOBAL_NAV_HEIGHT,
   LargeCenteredLayout,
+  PageContentFontWrapper,
+  Spinner,
   themeBorder,
   themeColor,
 } from 'design-system';
@@ -32,7 +34,6 @@ import { NavigateFunction, useNavigate, useParams } from 'react-router-dom';
 import { fetchQualityGates } from '../../../api/quality-gates';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import '../../../components/search-navigator.css';
-import Spinner from '../../../components/ui/Spinner';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
   addSideBarClass,
@@ -126,46 +127,48 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <LargeCenteredLayout id="quality-gates-page">
-        <Helmet
-          defer={false}
-          titleTemplate={translateWithParameters(
-            'page_title.template.with_category',
-            translate('quality_gates.page'),
-          )}
-        />
-        <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-w-full">
-          <Suggestions suggestions="quality_gates" />
-
-          <StyledContentWrapper
-            className="sw-col-span-3 sw-px-4 sw-py-6 sw-border-t-0 sw-rounded-0"
-            style={{
-              height: `calc(100vh - ${LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_FOOTER_HEIGHT}px)`,
-            }}
-          >
-            <ListHeader canCreate={canCreate} refreshQualityGates={this.fetchQualityGates} />
-            <Spinner loading={this.state.loading}>
-              <List qualityGates={qualityGates} currentQualityGate={name} />
-            </Spinner>
-          </StyledContentWrapper>
-
-          {name !== undefined && (
+        <PageContentFontWrapper className="sw-body-sm">
+          <Helmet
+            defer={false}
+            titleTemplate={translateWithParameters(
+              'page_title.template.with_category',
+              translate('quality_gates.page'),
+            )}
+          />
+          <div className="sw-grid sw-gap-x-12 sw-gap-y-6 sw-grid-cols-12 sw-w-full">
+            <Suggestions suggestions="quality_gates" />
+
             <StyledContentWrapper
-              className="sw-col-span-9 sw-overflow-y-auto sw-mt-12"
+              className="sw-col-span-3 sw-px-4 sw-py-6 sw-border-t-0 sw-rounded-0"
               style={{
-                height: `calc(100vh - ${
-                  LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_FOOTER_HEIGHT
-                }px - ${MAIN_CONTENT_TOP_PADDING}px)`,
+                height: `calc(100vh - ${LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_FOOTER_HEIGHT}px)`,
               }}
             >
-              <Details
-                qualityGateName={name}
-                onSetDefault={this.handleSetDefault}
-                qualityGates={this.state.qualityGates}
-                refreshQualityGates={this.fetchQualityGates}
-              />
+              <ListHeader canCreate={canCreate} refreshQualityGates={this.fetchQualityGates} />
+              <Spinner loading={this.state.loading}>
+                <List qualityGates={qualityGates} currentQualityGate={name} />
+              </Spinner>
             </StyledContentWrapper>
-          )}
-        </div>
+
+            {name !== undefined && (
+              <StyledContentWrapper
+                className="sw-col-span-9 sw-overflow-y-auto sw-mt-12"
+                style={{
+                  height: `calc(100vh - ${
+                    LAYOUT_GLOBAL_NAV_HEIGHT + LAYOUT_FOOTER_HEIGHT
+                  }px - ${MAIN_CONTENT_TOP_PADDING}px)`,
+                }}
+              >
+                <Details
+                  qualityGateName={name}
+                  onSetDefault={this.handleSetDefault}
+                  qualityGates={this.state.qualityGates}
+                  refreshQualityGates={this.fetchQualityGates}
+                />
+              </StyledContentWrapper>
+            )}
+          </div>
+        </PageContentFontWrapper>
       </LargeCenteredLayout>
     );
   }
index 5dc0018f5f2a97f8f92ee227f2045ecba8cd88ec..f3584343672181cb219d6bb640871771d60b3263 100644 (file)
@@ -23,7 +23,10 @@ import { CheckIcon, LightLabel } from 'design-system';
 import * as React from 'react';
 import { translate } from '../../../helpers/l10n';
 
-export default function CaycConditionsListItem({ index, last }: { index: number; last: boolean }) {
+export default function CaycConditionsListItem({
+  index,
+  last,
+}: Readonly<{ index: number; last: boolean }>) {
   return (
     <li className={classNames('sw-flex', { 'sw-mb-2': !last })}>
       <CheckIcon className="sw-mr-1 sw-pt-1/2" />
index c65492aa46285dea87cd35dd4b61b060fd66efd7..1f9694811da72c371fd0c302d3a23081ec9e2e5a 100644 (file)
@@ -42,7 +42,7 @@ interface Props {
   qualityGate: QualityGate;
 }
 
-export default function CaycReviewUpdateConditionsModal(props: Props) {
+export default function CaycReviewUpdateConditionsModal(props: Readonly<Props>) {
   const {
     conditions,
     qualityGate,
@@ -56,12 +56,12 @@ export default function CaycReviewUpdateConditionsModal(props: Props) {
   const { weakConditions, missingConditions } = getWeakMissingAndNonCaycConditions(conditions);
   const sortedWeakConditions = sortBy(
     weakConditions,
-    (condition) => metrics[condition.metric] && metrics[condition.metric].name,
+    (condition) => metrics[condition.metric]?.name,
   );
 
   const sortedMissingConditions = sortBy(
     missingConditions,
-    (condition) => metrics[condition.metric] && metrics[condition.metric].name,
+    (condition) => metrics[condition.metric]?.name,
   );
 
   const getDocUrl = useDocUrl();
index 190f3bd885768a4f8535872cc4b2ad7a7d38b454..d3eb135958ec51aeecdfb1dd53765b046432124b 100644 (file)
@@ -51,7 +51,7 @@ function ConditionValueDescription({
   appState: { settings },
   metric,
   isToBeModified = false,
-}: Props) {
+}: Readonly<Props>) {
   if (condition.metric === MetricKey.new_maintainability_rating) {
     const maintainabilityGrid = getMaintainabilityGrid(
       settings[GlobalSettingKeys.RatingGrid] ?? '',
index 544c817564e772c018c786b09cedbb7f418a0fa9..83d9dedee919eb358cfe030835a7a834e58eb923 100644 (file)
@@ -87,6 +87,7 @@ export function Conditions({
   const [editing, setEditing] = React.useState<boolean>(
     qualityGate.caycStatus === CaycStatus.NonCompliant,
   );
+  const { name } = qualityGate;
   const canEdit = Boolean(qualityGate.actions?.manageConditions);
   const { conditions = [] } = qualityGate;
   const existingConditions = conditions.filter((condition) => metrics[condition.metric]);
@@ -111,9 +112,11 @@ export function Conditions({
 
   const getDocUrl = useDocUrl();
 
+  // set edit only when the name is change
+  // i.e when user changes the quality gate
   React.useEffect(() => {
     setEditing(qualityGate.caycStatus === CaycStatus.NonCompliant);
-  }, [qualityGate]);
+  }, [name]); // eslint-disable-line react-hooks/exhaustive-deps
 
   const renderConditionModal = React.useCallback(
     ({ onClose }: ModalProps) => {
@@ -322,7 +325,7 @@ export function Conditions({
       )}
 
       {qualityGate.caycStatus !== CaycStatus.NonCompliant && !editing && canEdit && (
-        <div className="sw-mt-4 sw-mb-10 it__qg-unfollow-cayc">
+        <div className="sw-mt-4 it__qg-unfollow-cayc">
           <SubHeading as="p" className="sw-mb-2 sw-body-sm">
             <FormattedMessage
               id="quality_gates.cayc_unfollow.description"
index be2849cdf86fb364ee18fd16f2815522bc2ad360..92b6c630adeeaf633d6c9526ad8f0a78e73511be 100644 (file)
@@ -71,6 +71,7 @@ export class CreateQualityGateForm extends React.PureComponent<Props, State> {
           requiredAriaLabel={translate('field_required')}
         >
           <InputField
+            className="sw-mb-1"
             autoComplete="off"
             id="quality-gate-form-name"
             maxLength={256}
index ac614d3f48f26f327be36147d589ee0005689660..198111715ecb84a2133077d6ca454dbe60d55176 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { Spinner } from 'design-system';
 import { clone } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
 import { fetchQualityGate } from '../../../api/quality-gates';
-import Spinner from '../../../components/ui/Spinner';
 import { addGlobalSuccessMessage } from '../../../helpers/globalMessages';
 import { translate } from '../../../helpers/l10n';
 import { Condition, QualityGate } from '../../../types/types';
index 24617cc38504818a49d8fc55663a211ff3fd17cc..4afd92318dd38b91f2cb39778a14d93f8242a805 100644 (file)
@@ -56,7 +56,7 @@ export function DetailsContent(props: DetailsContentProps) {
         updatedConditionId={updatedConditionId}
       />
 
-      <div>
+      <div className="sw-mt-10">
         <div className="sw-flex sw-flex-col">
           <SubTitle as="h3" className="sw-body-md-highlight">
             {translate('quality_gates.projects')}
index 92da185ae618cea0ea485f489477dc6a2948288e..6c16f667f856d0e8ad6507d1ff0081186cd214d1 100644 (file)
@@ -73,7 +73,9 @@ export default function List({ qualityGates, currentQualityGate }: Props) {
 
               {(qualityGate.isDefault || qualityGate.isBuiltIn) && (
                 <div className="sw-mt-2">
-                  {qualityGate.isDefault && <Badge>{translate('default')}</Badge>}
+                  {qualityGate.isDefault && (
+                    <Badge className="sw-mr-2">{translate('default')}</Badge>
+                  )}
                   {qualityGate.isBuiltIn && <BuiltInQualityGateBadge />}
                 </div>
               )}
index 7782337632b43c62e482540558f0eeab1a374c48..0a7aef68925841444a8a4f13b375b5c0f5a5b554 100644 (file)
@@ -17,6 +17,8 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+
+import { Note } from 'design-system';
 import { find, without } from 'lodash';
 import * as React from 'react';
 import {
@@ -131,14 +133,14 @@ export default class Projects extends React.PureComponent<Props, State> {
   renderElement = (key: string): React.ReactNode => {
     const project = find(this.state.projects, { key });
     return (
-      <div className="select-list-list-item">
+      <div>
         {project === undefined ? (
           key
         ) : (
           <>
             {project.name}
             <br />
-            <span className="note">{project.key}</span>
+            <Note>{project.key}</Note>
           </>
         )}
       </div>
index 9dcfd8eb17aedaf992e1d0e0ab20b3b51ec8abcd..42c028346480abcee2ac92503c47e8c9209020ee 100644 (file)
@@ -23,6 +23,7 @@ import {
   GenericAvatar,
   LabelValueSelectOption,
   Modal,
+  Note,
   SearchSelectDropdown,
   UserGroupIcon,
 } from 'design-system';
@@ -49,7 +50,7 @@ const FORM_ID = 'quality-gate-permissions-add-modal';
 const USER_SELECT_INPUT_ID = 'quality-gate-permissions-add-modal-select-input';
 
 export default function QualityGatePermissionsAddModalRenderer(
-  props: QualityGatePermissionsAddModalRendererProps,
+  props: Readonly<QualityGatePermissionsAddModalRendererProps>,
 ) {
   const { selection, submitting } = props;
 
@@ -68,6 +69,7 @@ export default function QualityGatePermissionsAddModalRenderer(
             htmlFor={USER_SELECT_INPUT_ID}
           >
             <SearchSelectDropdown
+              className="sw-mb-2"
               controlAriaLabel={translate('quality_gates.permissions.search')}
               inputId={USER_SELECT_INPUT_ID}
               autoFocus
@@ -99,10 +101,10 @@ export default function QualityGatePermissionsAddModalRenderer(
 function OptionRenderer({
   option,
   small = false,
-}: {
+}: Readonly<{
   option?: UserBase | UserGroup;
   small?: boolean;
-}) {
+}>) {
   if (!option) {
     return null;
   }
@@ -118,7 +120,7 @@ function OptionRenderer({
           />
           <span className="sw-ml-2">
             <strong className="sw-body-sm-highlight sw-mr-1">{option.name}</strong>
-            {option.login}
+            <Note>{option.login}</Note>
           </span>
         </>
       ) : (
index 5434dd3736cb2d7d7ebe4251cc9152254c3d7431..877bfa4aadcc5113ab5dab6006500a5175dde88b 100644 (file)
@@ -17,6 +17,7 @@
  * 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 {
   ButtonSecondary,
   DangerButtonPrimary,
@@ -60,8 +61,8 @@ export default function QualityGatePermissionsRenderer(props: QualityGatePermiss
       <SubTitle as="h3" className="sw-body-md-highlight">
         {translate('quality_gates.permissions')}
       </SubTitle>
-      <p className="sw-body-sm sw-mb-2">{translate('quality_gates.permissions.help')}</p>
-      <div>
+      <p className="sw-body-sm">{translate('quality_gates.permissions.help')}</p>
+      <div className={classNames({ 'sw-my-2': users.length + groups.length > 0 })}>
         <Spinner loading={loading}>
           <Table columnCount={3} columnWidths={['40px', 'auto', '1%']} width="100%">
             {users.map((user) => (
@@ -78,7 +79,7 @@ export default function QualityGatePermissionsRenderer(props: QualityGatePermiss
         </Spinner>
       </div>
 
-      <ButtonSecondary className="sw-mt-4" onClick={props.onClickAddPermission}>
+      <ButtonSecondary className="sw-mt-2" onClick={props.onClickAddPermission}>
         {translate('quality_gates.permissions.grant')}
       </ButtonSecondary>
 
index 033a6aeed1c0f8c8f40e88fa2f42893dea0d2aa8..f68b0e6e623273c9f9cd87336a3958f09599d1a0 100644 (file)
@@ -19,7 +19,6 @@
  */
 import styled from '@emotion/styled';
 import { Checkbox, ListItem, UnorderedList, themeBorder } from 'design-system';
-import { uniqueId } from 'lodash';
 import * as React from 'react';
 import { translate } from '../../helpers/l10n';
 import { SelectListFilter } from './SelectList';
@@ -111,7 +110,7 @@ export default class SelectListListContainer extends React.PureComponent<Props,
             <SelectListListElement
               disabled={this.isDisabled(element)}
               element={element}
-              key={uniqueId()}
+              key={element}
               onSelect={this.props.onSelect}
               onUnselect={this.props.onUnselect}
               renderElement={this.props.renderElement}