]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19604 - Migrate activity sidebar
authorKevin Silva <kevin.silva@sonarsource.com>
Wed, 21 Jun 2023 13:56:44 +0000 (15:56 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Jun 2023 20:03:54 +0000 (20:03 +0000)
15 files changed:
server/sonar-web/design-system/src/components/Dropdown.tsx
server/sonar-web/design-system/src/components/icons/Icon.tsx
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/src/main/js/apps/projectActivity/components/Event.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/Events.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysesList.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityAnalysis.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/AddEventForm.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/ChangeEventForm.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveAnalysisForm.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/forms/RemoveEventForm.tsx
server/sonar-web/src/main/js/components/activity-graph/DefinitionChangeEventInner.tsx
server/sonar-web/src/main/js/components/activity-graph/EventInner.tsx
server/sonar-web/src/main/js/components/activity-graph/RichQualityGateEventInner.tsx

index 18b8ab9f1384c4bd4680fc45742383184f6d7ac5..c7d7b530e3e3c5691ced9b965305e1b52155ce66 100644 (file)
@@ -140,17 +140,18 @@ export class Dropdown extends React.PureComponent<Props, State> {
 }
 
 interface ActionsDropdownProps extends Omit<Props, 'children' | 'overlay'> {
+  ariaLabel?: string;
   buttonSize?: 'small' | 'medium';
   children: React.ReactNode;
 }
 
 export function ActionsDropdown(props: ActionsDropdownProps) {
-  const { children, buttonSize, ...dropdownProps } = props;
+  const { children, buttonSize, ariaLabel, ...dropdownProps } = props;
   return (
     <Dropdown overlay={children} {...dropdownProps}>
       <InteractiveIcon
         Icon={MenuIcon}
-        aria-label={translate('menu')}
+        aria-label={ariaLabel ?? translate('menu')}
         size={buttonSize}
         stopPropagation={false}
       />
index b0527ae437081818de2db75fe14119bcdc4df2e3..eca33edc13561cc5eae52bc18c05dc8e1bc4269a 100644 (file)
@@ -36,6 +36,7 @@ interface Props {
 export interface IconProps extends Omit<Props, 'children'> {
   fill?: ThemeColors | CSSColor;
   height?: number;
+  transform?: string;
   width?: number;
 }
 
index dbe05f21f679f09c0e9bd4f64c71539b14750e76..c8d59d982b1517ffbd5458c8406a2da7be872b46 100644 (file)
@@ -33,7 +33,7 @@ export * from './DatePicker';
 export * from './DateRangePicker';
 export { DeferredSpinner } from './DeferredSpinner';
 export * from './DiscreetSelect';
-export { Dropdown } from './Dropdown';
+export { ActionsDropdown, Dropdown } from './Dropdown';
 export * from './DropdownMenu';
 export { DropdownToggler } from './DropdownToggler';
 export * from './DuplicationsIndicator';
index b9bee0fde226a53701de569019fe05b2aed63758..ec0414f1bc8e8e63b4f6159dfa3f4519ebc09008 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 { DestructiveIcon, InteractiveIcon, PencilIcon, TrashIcon } from 'design-system';
 import * as React from 'react';
 import EventInner from '../../../components/activity-graph/EventInner';
-import { DeleteButton, EditButton } from '../../../components/controls/buttons';
+import Tooltip from '../../../components/controls/Tooltip';
 import { translate } from '../../../helpers/l10n';
 import { AnalysisEvent, ProjectAnalysisEventCategory } from '../../../types/project-activity';
 import ChangeEventForm from './forms/ChangeEventForm';
@@ -47,30 +48,36 @@ function Event(props: EventProps) {
   const showActions = canAdmin && (canChange || canDelete);
 
   return (
-    <div className="project-activity-event">
+    <div className="it__project-activity-event sw-flex sw-justify-between">
       <EventInner event={event} />
 
       {showActions && (
-        <span className="nowrap">
+        <div className="sw-grow-0 sw-shrink-0 sw-ml-2">
           {canChange && (
-            <EditButton
-              aria-label={translate('project_activity.events.tooltip.edit')}
-              className="button-small"
-              data-test="project-activity__edit-event"
-              onClick={() => setChanging(true)}
-              stopPropagation
-            />
+            <Tooltip overlay={translate('project_activity.events.tooltip.edit')}>
+              <InteractiveIcon
+                Icon={PencilIcon}
+                aria-label={translate('project_activity.events.tooltip.edit')}
+                data-test="project-activity__edit-event"
+                onClick={() => setChanging(true)}
+                stopPropagation
+                size="small"
+              />
+            </Tooltip>
           )}
           {canDelete && (
-            <DeleteButton
-              aria-label={translate('project_activity.events.tooltip.delete')}
-              className="button-small"
-              data-test="project-activity__delete-event"
-              onClick={() => setDeleting(true)}
-              stopPropagation
-            />
+            <Tooltip overlay={translate('project_activity.events.tooltip.delete')}>
+              <DestructiveIcon
+                Icon={TrashIcon}
+                aria-label={translate('project_activity.events.tooltip.delete')}
+                data-test="project-activity__delete-event"
+                onClick={() => setDeleting(true)}
+                stopPropagation
+                size="small"
+              />
+            </Tooltip>
           )}
-        </span>
+        </div>
       )}
 
       {changing && props.onChange && (
index 470ef7ccc80d80532d1e19695ece86dcf98ba989..f515d35637053217313be7af1e57c603a2e1f367 100644 (file)
@@ -43,7 +43,7 @@ function Events(props: EventsProps) {
   );
 
   return (
-    <div className="big-spacer-top">
+    <div className="sw-flex sw-flex-1 sw-flex-col sw-gap-1">
       {sortedEvents.map((event) => (
         <Event
           analysisKey={analysisKey}
index f6712cb4efe9b81c763823f456e7c89a9b793695..c88c67e0dbb6d8e4e7e990f13ce56b9deb069b4a 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 styled from '@emotion/styled';
 import classNames from 'classnames';
 import { isEqual } from 'date-fns';
+import { Badge, DeferredSpinner, themeColor } from 'design-system';
 import * as React from 'react';
 import Tooltip from '../../../components/controls/Tooltip';
 import DateFormatter from '../../../components/intl/DateFormatter';
 import { toShortISO8601String } from '../../../helpers/dates';
 import { translate } from '../../../helpers/l10n';
+
 import { ComponentQualifier } from '../../../types/component';
 import { ParsedAnalysis } from '../../../types/project-activity';
-import { activityQueryChanged, getAnalysesByVersionByDay, Query } from '../utils';
+import { Query, activityQueryChanged, getAnalysesByVersionByDay } from '../utils';
 import ProjectActivityAnalysis from './ProjectActivityAnalysis';
 
 interface Props {
@@ -99,13 +102,13 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro
       (byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0);
     if (this.props.analyses.length === 0 || !hasFilteredData) {
       return (
-        <div className="boxed-group-inner">
+        <div>
           {this.props.initializing ? (
-            <div className="text-center">
-              <i className="spinner" />
+            <div className="sw-p-4 sw-body-sm">
+              <DeferredSpinner />
             </div>
           ) : (
-            <span className="note">{translate('no_results')}</span>
+            <div className="sw-p-4 sw-body-sm">{translate('no_results')}</div>
           )}
         </div>
       );
@@ -113,7 +116,7 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro
 
     return (
       <ul
-        className="project-activity-versions-list"
+        className="it__project-activity-versions-list sw-box-border sw-max-w-abs-400 sw-overflow-auto sw-grow sw-shrink-0 sw-py-0 sw-px-4"
         ref={(element) => (this.scrollContainer = element)}
         style={{
           marginTop:
@@ -127,29 +130,37 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro
           if (days.length <= 0) {
             return null;
           }
+
           return (
             <li key={version.key || 'noversion'}>
               {version.version && (
-                <div className={classNames('project-activity-version-badge', { first: idx === 0 })}>
+                <VersionTagStyled
+                  className={classNames(
+                    'sw-sticky sw-top-0 sw-left-0 sw-pb-1 -sw-ml-4 sw-pt-3 sw-z-normal',
+                    {
+                      'sw-top-0 sw-pt-0': idx === 0,
+                    }
+                  )}
+                >
                   <Tooltip
                     mouseEnterDelay={0.5}
                     overlay={`${translate('version')} ${version.version}`}
                   >
-                    <h2 className="analysis-version">{version.version}</h2>
+                    <Badge className="sw-p-1">{version.version}</Badge>
                   </Tooltip>
-                </div>
+                </VersionTagStyled>
               )}
-              <ul className="project-activity-days-list">
+              <ul className="it__project-activity-days-list">
                 {days.map((day) => (
                   <li
-                    className="project-activity-day"
+                    className="it__project-activity-day sw-mt-1 sw-mb-4"
                     data-day={toShortISO8601String(Number(day))}
                     key={day}
                   >
-                    <h3>
+                    <div className="sw-body-md-highlight sw-mb-3">
                       <DateFormatter date={Number(day)} long />
-                    </h3>
-                    <ul className="project-activity-analyses-list">
+                    </div>
+                    <ul className="it__project-activity-analyses-list">
                       {version.byDay[day] != null &&
                         version.byDay[day].map((analysis) => this.renderAnalysis(analysis))}
                     </ul>
@@ -160,11 +171,15 @@ export default class ProjectActivityAnalysesList extends React.PureComponent<Pro
           );
         })}
         {this.props.analysesLoading && (
-          <li className="text-center">
-            <i className="spinner" />
+          <li className="sw-text-center">
+            <DeferredSpinner />
           </li>
         )}
       </ul>
     );
   }
 }
+
+const VersionTagStyled = styled.div`
+  background-color: ${themeColor('backgroundSecondary')};
+`;
index 806528dcf738e836a59a15c7470eb1818c15bce5..5009c41e566d703f617269a4269a773fbf4fa304 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 styled from '@emotion/styled';
 import classNames from 'classnames';
+import {
+  ActionsDropdown,
+  ItemButton,
+  ItemDangerButton,
+  ItemDivider,
+  PopupZLevel,
+  themeBorder,
+  themeColor,
+} from 'design-system';
 import * as React from 'react';
-import { injectIntl, WrappedComponentProps } from 'react-intl';
-import ActionsDropdown, {
-  ActionsDropdownDivider,
-  ActionsDropdownItem,
-} from '../../../components/controls/ActionsDropdown';
-import { ButtonPlain } from '../../../components/controls/buttons';
-import ClickEventBoundary from '../../../components/controls/ClickEventBoundary';
+import { WrappedComponentProps, injectIntl } from 'react-intl';
 import HelpTooltip from '../../../components/controls/HelpTooltip';
+import Tooltip from '../../../components/controls/Tooltip';
 import { formatterOption } from '../../../components/intl/DateTimeFormatter';
 import TimeFormatter from '../../../components/intl/TimeFormatter';
-import { PopupPlacement } from '../../../components/ui/popups';
 import { parseDate } from '../../../helpers/dates';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { ParsedAnalysis } from '../../../types/project-activity';
@@ -68,7 +72,7 @@ function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) {
 
   React.useEffect(() => {
     if (node && selected) {
-      node.scrollIntoView({ behavior: 'smooth' });
+      node.scrollIntoView({ behavior: 'smooth', block: 'center' });
     }
   });
 
@@ -84,140 +88,155 @@ function ProjectActivityAnalysis(props: ProjectActivityAnalysisProps) {
   const canDeleteAnalyses =
     props.canDeleteAnalyses && !isFirst && !analysis.manualNewCodePeriodBaseline;
 
+  let tooltipContent = <TimeFormatter date={parsedDate} long />;
+  if (analysis.buildString) {
+    tooltipContent = (
+      <>
+        {tooltipContent}
+        {translateWithParameters('project_activity.analysis_build_string_X', analysis.buildString)}
+      </>
+    );
+  }
+
   return (
-    <li
-      className={classNames('project-activity-analysis bordered-top bordered-bottom', {
-        selected,
-      })}
-      onClick={() => props.onUpdateSelectedDate(analysis.date)}
-      ref={(ref) => (node = ref)}
-    >
-      <div className="display-flex-center display-flex-space-between">
-        <div className="project-activity-time">
-          <TimeFormatter date={parsedDate} long={false}>
-            {(formattedTime) => (
-              <ButtonPlain
-                aria-current={selected}
-                aria-label={translateWithParameters(
-                  'project_activity.show_analysis_X_on_graph',
-                  analysis.buildString || formatDate(parsedDate, formatterOption)
-                )}
-                onClick={() => props.onUpdateSelectedDate(analysis.date)}
-              >
-                <time className="text-middle" dateTime={parsedDate.toISOString()}>
-                  {formattedTime}
-                </time>
-              </ButtonPlain>
-            )}
-          </TimeFormatter>
+    <Tooltip mouseEnterDelay={0.5} overlay={tooltipContent} placement="left">
+      <ActivityAnalysisListItem
+        className={classNames(
+          'it__project-activity-analysis sw-flex sw-cursor-pointer sw-p-1 sw-relative',
+          {
+            active: selected,
+          }
+        )}
+        aria-label={translateWithParameters(
+          'project_activity.show_analysis_X_on_graph',
+          analysis.buildString || formatDate(parsedDate, formatterOption)
+        )}
+        onClick={() => props.onUpdateSelectedDate(analysis.date)}
+        ref={(ref) => (node = ref)}
+      >
+        <div className="it__project-activity-time">
+          <ActivityTime className="sw-box-border sw-grow-0 sw-shrink-0 sw-h-page sw-body-sm-highlight sw-text-right sw-mr-2">
+            <TimeFormatter date={parsedDate} long={false}>
+              {(formattedTime) => <time dateTime={parsedDate.toISOString()}>{formattedTime}</time>}
+            </TimeFormatter>
+          </ActivityTime>
         </div>
 
-        {analysis.buildString && (
-          <div className="flex-shrink small text-muted text-ellipsis">
-            {translateWithParameters(
-              'project_activity.analysis_build_string_X',
-              analysis.buildString
+        {(canAddVersion || canAddEvent || canDeleteAnalyses) && (
+          <div className="sw-h-page sw-grow-0 sw-shrink-0 sw-mr-4 sw-relative">
+            <ActionsDropdown
+              ariaLabel={translateWithParameters(
+                'project_activity.analysis_X_actions',
+                analysis.buildString || formatDate(parsedDate, formatterOption)
+              )}
+              buttonSize="small"
+              id="it__analysis-actions"
+              zLevel={PopupZLevel.Absolute}
+            >
+              {canAddVersion && (
+                <ItemButton className="js-add-version" onClick={() => setAddVersionForm(true)}>
+                  {translate('project_activity.add_version')}
+                </ItemButton>
+              )}
+              {canAddEvent && (
+                <ItemButton className="js-add-event" onClick={() => setAddEventForm(true)}>
+                  {translate('project_activity.add_custom_event')}
+                </ItemButton>
+              )}
+              {(canAddVersion || canAddEvent) && canDeleteAnalyses && <ItemDivider />}
+              {canDeleteAnalyses && (
+                <ItemDangerButton
+                  className="js-delete-analysis"
+                  onClick={() => setRemoveAnalysisForm(true)}
+                >
+                  {translate('project_activity.delete_analysis')}
+                </ItemDangerButton>
+              )}
+            </ActionsDropdown>
+
+            {addVersionForm && (
+              <AddEventForm
+                addEvent={props.onAddVersion}
+                addEventButtonText="project_activity.add_version"
+                analysis={analysis}
+                onClose={() => setAddVersionForm(false)}
+              />
+            )}
+
+            {addEventForm && (
+              <AddEventForm
+                addEvent={props.onAddCustomEvent}
+                addEventButtonText="project_activity.add_custom_event"
+                analysis={analysis}
+                onClose={() => setAddEventForm(false)}
+              />
+            )}
+
+            {removeAnalysisForm && (
+              <RemoveAnalysisForm
+                analysis={analysis}
+                deleteAnalysis={props.onDeleteAnalysis}
+                onClose={() => setRemoveAnalysisForm(false)}
+              />
             )}
           </div>
         )}
 
-        {(canAddVersion || canAddEvent || canDeleteAnalyses) && (
-          <ClickEventBoundary>
-            <div className="project-activity-analysis-actions big-spacer-left">
-              <ActionsDropdown
-                label={translateWithParameters(
-                  'project_activity.analysis_X_actions',
-                  analysis.buildString || formatDate(parsedDate, formatterOption)
-                )}
-                overlayPlacement={PopupPlacement.BottomRight}
-                small
-                toggleClassName="js-analysis-actions"
-              >
-                {canAddVersion && (
-                  <ActionsDropdownItem
-                    className="js-add-version"
-                    onClick={() => setAddVersionForm(true)}
-                  >
-                    {translate('project_activity.add_version')}
-                  </ActionsDropdownItem>
-                )}
-                {canAddEvent && (
-                  <ActionsDropdownItem
-                    className="js-add-event"
-                    onClick={() => setAddEventForm(true)}
-                  >
-                    {translate('project_activity.add_custom_event')}
-                  </ActionsDropdownItem>
-                )}
-                {(canAddVersion || canAddEvent) && canDeleteAnalyses && <ActionsDropdownDivider />}
-                {canDeleteAnalyses && (
-                  <ActionsDropdownItem
-                    className="js-delete-analysis"
-                    destructive
-                    onClick={() => setRemoveAnalysisForm(true)}
-                  >
-                    {translate('project_activity.delete_analysis')}
-                  </ActionsDropdownItem>
-                )}
-              </ActionsDropdown>
-
-              {addVersionForm && (
-                <AddEventForm
-                  addEvent={props.onAddVersion}
-                  addEventButtonText="project_activity.add_version"
-                  analysis={analysis}
-                  onClose={() => setAddVersionForm(false)}
-                />
-              )}
-
-              {addEventForm && (
-                <AddEventForm
-                  addEvent={props.onAddCustomEvent}
-                  addEventButtonText="project_activity.add_custom_event"
-                  analysis={analysis}
-                  onClose={() => setAddEventForm(false)}
-                />
-              )}
+        {analysis.events.length > 0 && (
+          <Events
+            analysisKey={analysis.key}
+            canAdmin={canAdmin}
+            events={analysis.events}
+            isFirst={isFirst}
+            onChange={props.onChangeEvent}
+            onDelete={props.onDeleteEvent}
+          />
+        )}
 
-              {removeAnalysisForm && (
-                <RemoveAnalysisForm
-                  analysis={analysis}
-                  deleteAnalysis={props.onDeleteAnalysis}
-                  onClose={() => setRemoveAnalysisForm(false)}
-                />
-              )}
+        {isBaseline && (
+          <div className="baseline-marker">
+            <div className="wedge" />
+            <hr />
+            <div className="label display-flex-center">
+              {translate('project_activity.new_code_period_start')}
+              <HelpTooltip
+                className="little-spacer-left"
+                overlay={translate('project_activity.new_code_period_start.help')}
+                placement="top"
+              />
             </div>
-          </ClickEventBoundary>
-        )}
-      </div>
-
-      {analysis.events.length > 0 && (
-        <Events
-          analysisKey={analysis.key}
-          canAdmin={canAdmin}
-          events={analysis.events}
-          isFirst={isFirst}
-          onChange={props.onChangeEvent}
-          onDelete={props.onDeleteEvent}
-        />
-      )}
-
-      {isBaseline && (
-        <div className="baseline-marker">
-          <div className="wedge" />
-          <hr />
-          <div className="label display-flex-center">
-            {translate('project_activity.new_code_period_start')}
-            <HelpTooltip
-              className="little-spacer-left"
-              overlay={translate('project_activity.new_code_period_start.help')}
-              placement="top"
-            />
           </div>
-        </div>
-      )}
-    </li>
+        )}
+      </ActivityAnalysisListItem>
+    </Tooltip>
   );
 }
 
+const ActivityTime = styled.div`
+  width: 4.5rem;
+`;
+
+const ActivityAnalysisListItem = styled.li`
+  border-bottom: ${themeBorder('default')};
+  border-left: ${themeBorder('active', 'transparent')};
+
+  &:first-of-type {
+    border-top: ${themeBorder('default')};
+  }
+
+  &:focus {
+    outline: none;
+  }
+
+  &:hover,
+  &:focus,
+  &.active {
+    background-color: ${themeColor('subnavigationHover')};
+  }
+
+  &.active {
+    border-left: ${themeBorder('active')};
+  }
+`;
+
 export default injectIntl(ProjectActivityAnalysis);
index bcad96af0d8201650b802abbe2776e2bffcdcc2b..9ac37d471371462f5fabe703dc53b6e1ec8859bb 100644 (file)
@@ -169,11 +169,13 @@ describe('CRUD', () => {
     await ui.appLoaded();
 
     await ui.addVersionEvent('1.1.0.1', initialValue);
-    expect(screen.getAllByText(initialValue)[0]).toBeInTheDocument();
+    // should appear 3x, one for the list, one for the graph and one for the tooltip
+    expect(screen.getAllByText(initialValue).length).toEqual(3);
 
     await act(async () => {
       await ui.updateEvent(1, updatedValue);
-      expect(screen.getAllByText(updatedValue)[0]).toBeInTheDocument();
+      // should appear 3x, one for the list, one for the graph and one for the tooltip
+      expect(screen.getAllByText(updatedValue).length).toEqual(3);
     });
 
     await ui.deleteEvent(0);
@@ -198,12 +200,12 @@ describe('CRUD', () => {
 
     await act(async () => {
       await ui.addCustomEvent('1.1.0.1', initialValue);
-      expect(screen.getByText(initialValue)).toBeInTheDocument();
+      expect(screen.getAllByText(initialValue)[0]).toBeInTheDocument();
     });
 
     await act(async () => {
       await ui.updateEvent(1, updatedValue);
-      expect(screen.getByText(updatedValue)).toBeInTheDocument();
+      expect(screen.getAllByText(updatedValue)[0]).toBeInTheDocument();
     });
 
     await ui.deleteEvent(0);
@@ -428,10 +430,10 @@ function getPageObject() {
     activityItem: byLabelText(/project_activity.show_analysis_X_on_graph/),
     cogBtn: (id: string) => byRole('button', { name: `project_activity.analysis_X_actions.${id}` }),
     seeDetailsBtn: (time: string) =>
-      byRole('button', { name: `project_activity.show_analysis_X_on_graph.${time}` }),
-    addCustomEventBtn: byRole('button', { name: 'project_activity.add_custom_event' }),
-    addVersionEvenBtn: byRole('button', { name: 'project_activity.add_version' }),
-    deleteAnalysisBtn: byRole('button', { name: 'project_activity.delete_analysis' }),
+      byLabelText(`project_activity.show_analysis_X_on_graph.${time}`),
+    addCustomEventBtn: byRole('menuitem', { name: 'project_activity.add_custom_event' }),
+    addVersionEvenBtn: byRole('menuitem', { name: 'project_activity.add_version' }),
+    deleteAnalysisBtn: byRole('menuitem', { name: 'project_activity.delete_analysis' }),
     editEventBtn: byRole('button', { name: 'project_activity.events.tooltip.edit' }),
     deleteEventBtn: byRole('button', { name: 'project_activity.events.tooltip.delete' }),
 
index cd1c398bc5def21bff7a9965780a2d82a0eee599..be1d457f3aa52cae7dfbfe1efd76e86fa23efa9b 100644 (file)
@@ -17,8 +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 { ButtonPrimary, InputField, Modal } from 'design-system';
 import * as React from 'react';
-import ConfirmModal from '../../../../components/controls/ConfirmModal';
 import { translate } from '../../../../helpers/l10n';
 import { ParsedAnalysis } from '../../../../types/project-activity';
 
@@ -41,30 +41,41 @@ export default class AddEventForm extends React.PureComponent<Props, State> {
   };
 
   handleSubmit = () => {
-    return this.props.addEvent(this.props.analysis.key, this.state.name);
+    this.props.addEvent(this.props.analysis.key, this.state.name);
+    this.props.onClose();
   };
 
   render() {
     return (
-      <ConfirmModal
-        confirmButtonText={translate('save')}
-        confirmDisable={!this.state.name}
-        header={translate(this.props.addEventButtonText)}
+      <Modal
+        headerTitle={translate(this.props.addEventButtonText)}
         onClose={this.props.onClose}
-        onConfirm={this.handleSubmit}
-        size="small"
-      >
-        <div className="modal-field">
-          <label htmlFor="name">{translate('name')}</label>
-          <input
-            id="name"
-            autoFocus
-            onChange={this.handleNameChange}
-            type="text"
-            value={this.state.name}
-          />
-        </div>
-      </ConfirmModal>
+        body={
+          <div>
+            <label htmlFor="name">{translate('name')}</label>
+            <InputField
+              id="name"
+              autoFocus
+              onChange={this.handleNameChange}
+              type="text"
+              value={this.state.name}
+              size="full"
+            />
+          </div>
+        }
+        primaryButton={
+          <ButtonPrimary
+            id="add-event-submit"
+            form="add-event-form"
+            type="submit"
+            disabled={!this.state.name}
+            onClick={this.handleSubmit}
+          >
+            {translate('save')}
+          </ButtonPrimary>
+        }
+        secondaryButtonLabel={translate('cancel')}
+      />
     );
   }
 }
index 5bc690cf845871a49813175bd96fc8965c59cd20..195b0b88f5144b8c37bbfbfae863d51e149f38ea 100644 (file)
@@ -17,8 +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 { ButtonPrimary, InputField, Modal } from 'design-system';
 import * as React from 'react';
-import ConfirmModal from '../../../../components/controls/ConfirmModal';
 import { translate } from '../../../../helpers/l10n';
 import { AnalysisEvent } from '../../../../types/project-activity';
 
@@ -44,25 +44,42 @@ export default class ChangeEventForm extends React.PureComponent<Props, State> {
   };
 
   handleSubmit = () => {
-    return this.props.changeEvent(this.props.event.key, this.state.name);
+    this.props.changeEvent(this.props.event.key, this.state.name);
+    this.props.onClose();
   };
 
   render() {
     const { name } = this.state;
     return (
-      <ConfirmModal
-        confirmButtonText={translate('change_verb')}
-        confirmDisable={!name || name === this.props.event.name}
-        header={this.props.header}
+      <Modal
+        headerTitle={this.props.header}
         onClose={this.props.onClose}
-        onConfirm={this.handleSubmit}
-        size="small"
-      >
-        <div className="modal-field">
-          <label htmlFor="name">{translate('name')}</label>
-          <input id="name" autoFocus onChange={this.changeInput} type="text" value={name} />
-        </div>
-      </ConfirmModal>
+        body={
+          <div>
+            <label htmlFor="name">{translate('name')}</label>
+            <InputField
+              id="name"
+              autoFocus
+              onChange={this.changeInput}
+              type="text"
+              value={name}
+              size="full"
+            />
+          </div>
+        }
+        primaryButton={
+          <ButtonPrimary
+            id="change-event-submit"
+            form="change-event-form"
+            type="submit"
+            disabled={!name || name === this.props.event.name}
+            onClick={this.handleSubmit}
+          >
+            {translate('change_verb')}
+          </ButtonPrimary>
+        }
+        secondaryButtonLabel={translate('cancel')}
+      />
     );
   }
 }
index ee5b3a845a326014195226e4b1f178d304d9e096..c9c4e3296472a9368200c8861a8679c9d7803176 100644 (file)
@@ -17,8 +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 { DangerButtonPrimary, Modal } from 'design-system';
 import * as React from 'react';
-import ConfirmModal from '../../../../components/controls/ConfirmModal';
 import { translate } from '../../../../helpers/l10n';
 import { ParsedAnalysis } from '../../../../types/project-activity';
 
@@ -30,15 +30,16 @@ interface Props {
 
 export default function RemoveAnalysisForm({ analysis, deleteAnalysis, onClose }: Props) {
   return (
-    <ConfirmModal
-      confirmButtonText={translate('delete')}
-      confirmData={analysis.key}
-      header={translate('project_activity.delete_analysis')}
-      isDestructive
+    <Modal
+      headerTitle={translate('project_activity.delete_analysis')}
       onClose={onClose}
-      onConfirm={deleteAnalysis}
-    >
-      {translate('project_activity.delete_analysis.question')}
-    </ConfirmModal>
+      body={<p>{translate('project_activity.delete_analysis.question')}</p>}
+      primaryButton={
+        <DangerButtonPrimary onClick={() => deleteAnalysis(analysis.key)} type="submit">
+          {translate('delete')}
+        </DangerButtonPrimary>
+      }
+      secondaryButtonLabel={translate('cancel')}
+    />
   );
 }
index 0a1be309f1488f6266cd9da58f0e3174a9d5d915..ba3b63b1cc5f854ae1d277b214ca9e70eb2c7005 100644 (file)
@@ -17,8 +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 { DangerButtonPrimary, Modal } from 'design-system';
 import * as React from 'react';
-import ConfirmModal from '../../../../components/controls/ConfirmModal';
 import { translate } from '../../../../helpers/l10n';
 import { AnalysisEvent } from '../../../../types/project-activity';
 
@@ -34,14 +34,16 @@ export interface RemoveEventFormProps {
 export default function RemoveEventForm(props: RemoveEventFormProps) {
   const { analysisKey, event, header, removeEventQuestion } = props;
   return (
-    <ConfirmModal
-      confirmButtonText={translate('delete')}
-      header={header}
-      isDestructive
+    <Modal
+      headerTitle={header}
       onClose={props.onClose}
-      onConfirm={() => props.onConfirm(analysisKey, event.key)}
-    >
-      {removeEventQuestion}
-    </ConfirmModal>
+      body={<p>{removeEventQuestion}</p>}
+      primaryButton={
+        <DangerButtonPrimary onClick={() => props.onConfirm(analysisKey, event.key)}>
+          {translate('delete')}
+        </DangerButtonPrimary>
+      }
+      secondaryButtonLabel={translate('cancel')}
+    />
   );
 }
index fbc09ec10fdade512fbcb432973c6ad8060bc8ce..dafa20f4d6a885ed000ca1909c8e7476859f81d1 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 { BareButton, ChevronDownIcon, StandoutLink } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { isMainBranch } from '../../helpers/branch-like';
@@ -29,11 +30,8 @@ import {
   ApplicationAnalysisEventCategory,
   DefinitionChangeType,
 } from '../../types/project-activity';
-import Link from '../common/Link';
-import { ButtonLink } from '../controls/buttons';
 import ClickEventBoundary from '../controls/ClickEventBoundary';
 import BranchIcon from '../icons/BranchIcon';
-import DropdownIcon from '../icons/DropdownIcon';
 
 export type DefinitionChangeEvent = AnalysisEvent &
   Required<Pick<AnalysisEvent, 'definitionChange'>>;
@@ -66,9 +64,9 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State
 
   renderProjectLink = (project: { key: string; name: string }, branch: string | undefined) => (
     <ClickEventBoundary>
-      <Link title={project.name} to={getProjectUrl(project.key, branch)}>
+      <StandoutLink title={project.name} to={getProjectUrl(project.key, branch)}>
         {limitComponentName(project.name, NAME_MAX_LENGTH)}
-      </Link>
+      </StandoutLink>
     </ClickEventBoundary>
   );
 
@@ -95,16 +93,14 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State
           ? 'event.definition_change.added'
           : 'event.definition_change.branch_added';
         return (
-          <div className="text-ellipsis">
-            <FormattedMessage
-              defaultMessage={translate(message)}
-              id={message}
-              values={{
-                project: this.renderProjectLink(project, project.branch),
-                branch: this.renderBranch(project.branch),
-              }}
-            />
-          </div>
+          <FormattedMessage
+            defaultMessage={translate(message)}
+            id={message}
+            values={{
+              project: this.renderProjectLink(project, project.branch),
+              branch: this.renderBranch(project.branch),
+            }}
+          />
         );
       }
 
@@ -113,16 +109,14 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State
           ? 'event.definition_change.removed'
           : 'event.definition_change.branch_removed';
         return (
-          <div className="text-ellipsis">
-            <FormattedMessage
-              defaultMessage={translate(message)}
-              id={message}
-              values={{
-                project: this.renderProjectLink(project, project.branch),
-                branch: this.renderBranch(project.branch),
-              }}
-            />
-          </div>
+          <FormattedMessage
+            defaultMessage={translate(message)}
+            id={message}
+            values={{
+              project: this.renderProjectLink(project, project.branch),
+              branch: this.renderBranch(project.branch),
+            }}
+          />
         );
       }
 
@@ -145,35 +139,30 @@ export class DefinitionChangeEventInner extends React.PureComponent<Props, State
     const { event, readonly } = this.props;
     const { expanded } = this.state;
     return (
-      <>
-        <span className="note">
-          {translate('event.category', event.category)}
-          {!readonly && ':'}
-        </span>
-
-        {!readonly && (
-          <div>
-            <ButtonLink
-              className="project-activity-event-inner-more-link"
-              onClick={this.toggleProjectsList}
-              stopPropagation
-            >
-              {expanded ? translate('hide') : translate('more')}
-              <DropdownIcon className="little-spacer-left" turned={expanded} />
-            </ButtonLink>
-          </div>
-        )}
+      <div className="sw-flex sw-basis-full sw-flex-col">
+        <div className="sw-flex sw-justify-between">
+          <span className="sw-mr-1">{translate('event.category', event.category)}</span>
+
+          {!readonly && (
+            <div>
+              <BareButton onClick={this.toggleProjectsList}>
+                {expanded ? translate('hide') : translate('more')}
+                <ChevronDownIcon transform={expanded ? 'rotate(180)' : undefined} />
+              </BareButton>
+            </div>
+          )}
+        </div>
 
         {expanded && (
-          <ul className="spacer-left spacer-top">
+          <ul className="sw-mt-2">
             {event.definitionChange.projects.map((project) => (
-              <li className="display-flex-center spacer-top" key={project.key}>
+              <li className="sw-p-1 sw-text-ellipsis sw-overflow-hidden" key={project.key}>
                 {this.renderProjectChange(project)}
               </li>
             ))}
           </ul>
         )}
-      </>
+      </div>
     );
   }
 }
index c792a7348747a98f65281264870f0c2d3de9cdda..2d50393af70e35f8a3936483622ade9f40c602f4 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 { Note } from 'design-system';
 import * as React from 'react';
 import { ComponentContext } from '../../app/components/componentContext/ComponentContext';
 import { translate } from '../../helpers/l10n';
 import { AnalysisEvent } from '../../types/project-activity';
 import Tooltip from '../controls/Tooltip';
 import { DefinitionChangeEventInner, isDefinitionChangeEvent } from './DefinitionChangeEventInner';
-import { isRichQualityGateEvent, RichQualityGateEventInner } from './RichQualityGateEventInner';
+import { RichQualityGateEventInner, isRichQualityGateEvent } from './RichQualityGateEventInner';
 
 export interface EventInnerProps {
   event: AnalysisEvent;
@@ -44,12 +45,19 @@ export default function EventInner({ event, readonly }: EventInnerProps) {
   }
   return (
     <Tooltip overlay={event.description}>
-      <span className="text-middle">
-        <span className="note little-spacer-right">
-          {translate('event.category', event.category)}:
-        </span>
-        <strong className="spacer-right">{event.name}</strong>
-      </span>
+      <div className="sw-min-w-0 sw-flex-1">
+        <div className="sw-flex sw-items-start">
+          <span>
+            <Note className="sw-mr-1 sw-body-sm-highlight">
+              {translate('event.category', event.category)}
+              {event.category === 'VERSION' && ':'}
+            </Note>
+            <Note className="sw-body-sm" title={event.description}>
+              {event.name}
+            </Note>
+          </span>
+        </div>
+      </div>
     </Tooltip>
   );
 }
index b843b98fc79e5ee6f8c3c8a5695a94d2eb8fcca1..dd00000e8c98f0211a05e9ea2ee8785a77e200a4 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 { BareButton, ChevronDownIcon, QualityGateIndicator, StandoutLink } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { translate, translateWithParameters } from '../../helpers/l10n';
 import { getProjectUrl } from '../../helpers/urls';
 import { AnalysisEvent } from '../../types/project-activity';
-import Link from '../common/Link';
-import { ResetButtonLink } from '../controls/buttons';
 import ClickEventBoundary from '../controls/ClickEventBoundary';
-import DropdownIcon from '../icons/DropdownIcon';
-import Level from '../ui/Level';
 
 export type RichQualityGateEvent = AnalysisEvent & Required<Pick<AnalysisEvent, 'qualityGate'>>;
 
@@ -54,55 +51,65 @@ export class RichQualityGateEventInner extends React.PureComponent<Props, State>
     const { event, readonly } = this.props;
     const { expanded } = this.state;
     return (
-      <>
-        <span className="note spacer-right">{translate('event.category', event.category)}:</span>
-        {event.qualityGate.stillFailing ? (
-          <FormattedMessage
-            defaultMessage={translate('event.quality_gate.still_x')}
-            id="event.quality_gate.still_x"
-            values={{ status: <Level level={event.qualityGate.status} small /> }}
-          />
-        ) : (
-          <Level level={event.qualityGate.status} small />
-        )}
+      <div className="sw-flex sw-basis-full sw-flex-col">
+        <div className="sw-flex sw-justify-between">
+          <div className="sw-flex sw-justify-between">
+            <span className="sw-mr-1">{translate('event.category', event.category)}:</span>
+            <div className="sw-mx-2">
+              {event.qualityGate.stillFailing ? (
+                <FormattedMessage
+                  defaultMessage={translate('event.quality_gate.still_x')}
+                  id="event.quality_gate.still_x"
+                  values={{
+                    status: <QualityGateIndicator status={event.qualityGate.status} size="sm" />,
+                  }}
+                />
+              ) : (
+                <QualityGateIndicator status={event.qualityGate.status} size="sm" />
+              )}
+            </div>
+            <span>{translate(`event.quality_gate.${event.qualityGate.status}`)}</span>
+          </div>
 
-        <div>
           {!readonly && event.qualityGate.failing.length > 0 && (
-            <ResetButtonLink
-              className="project-activity-event-inner-more-link"
-              onClick={this.toggleProjectsList}
-              stopPropagation
-            >
-              {expanded ? translate('hide') : translate('more')}
-              <DropdownIcon className="little-spacer-left" turned={expanded} />
-            </ResetButtonLink>
+            <div>
+              <BareButton onClick={this.toggleProjectsList}>
+                {expanded ? translate('hide') : translate('more')}
+                <ChevronDownIcon transform={expanded ? 'rotate(180)' : undefined} />
+              </BareButton>
+            </div>
           )}
         </div>
 
         {expanded && (
-          <ul className="spacer-left spacer-top">
+          <ul className="sw-flex sw-flex-col sw-mt-2">
             {event.qualityGate.failing.map((project) => (
-              <li className="display-flex-center spacer-top" key={project.key}>
-                <Level
-                  aria-label={translate('quality_gates.status')}
-                  className="spacer-right"
-                  level={event.qualityGate.status}
-                  small
-                />
-                <div className="flex-1 text-ellipsis">
+              <li className="sw-flex sw-p-1" key={project.key}>
+                <div>
                   <ClickEventBoundary>
-                    <Link title={project.name} to={getProjectUrl(project.key, project.branch)}>
+                    <StandoutLink
+                      title={project.name}
+                      to={getProjectUrl(project.key, project.branch)}
+                    >
                       <span aria-label={translateWithParameters('project_x', project.name)}>
                         {project.name}
                       </span>
-                    </Link>
+                    </StandoutLink>
                   </ClickEventBoundary>
                 </div>
+                <div className="sw-shrink sw-flex">
+                  <div className="sw-items-top">
+                    <QualityGateIndicator status={event.qualityGate.status} size="sm" />
+                  </div>
+                  <span className="sw-ml-2">
+                    {translate(`event.quality_gate.${event.qualityGate.status}`)}
+                  </span>
+                </div>
               </li>
             ))}
           </ul>
         )}
-      </>
+      </div>
     );
   }
 }