aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>2023-11-01 08:16:34 +0100
committersonartech <sonartech@sonarsource.com>2023-11-02 20:02:41 +0000
commitd7128452ed937748f7a5b8dd618d38cd17fd129b (patch)
tree5753cb5b31db51a6ac9099257ead7df0a488b356
parentf4e45ecfb6e09a1006ba3f0acddb02c8026f4028 (diff)
downloadsonarqube-d7128452ed937748f7a5b8dd618d38cd17fd129b.tar.gz
sonarqube-d7128452ed937748f7a5b8dd618d38cd17fd129b.zip
SONAR-20925 Migrating project's links page to new UI
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx1
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx115
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/Header.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkRow.tsx (renamed from server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx)67
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkTable.tsx (renamed from server/sonar-web/src/main/js/apps/projectLinks/Table.tsx)52
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx21
-rw-r--r--server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx2
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
8 files changed, 129 insertions, 145 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index dccd65c1304..f8c8b6c0ace 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -61,6 +61,7 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [
'/project/branches',
'/project/key',
'/project/deletion',
+ '/project/links',
];
export default function GlobalContainer() {
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx
index 30823e150ca..830dfb8ed81 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx
@@ -17,12 +17,9 @@
* 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, FormField, InputField, Modal } from 'design-system';
import * as React from 'react';
-import SimpleModal from '../../components/controls/SimpleModal';
-import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
-import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker';
import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation';
-import Spinner from '../../components/ui/Spinner';
import { translate } from '../../helpers/l10n';
interface Props {
@@ -35,10 +32,13 @@ interface State {
url: string;
}
+const FORM_ID = 'create-link-form';
+
export default class CreationModal extends React.PureComponent<Props, State> {
state: State = { name: '', url: '' };
- handleSubmit = () => {
+ handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
return this.props.onSubmit(this.state.name, this.state.url).then(this.props.onClose);
};
@@ -53,68 +53,55 @@ export default class CreationModal extends React.PureComponent<Props, State> {
render() {
const header = translate('project_links.create_new_project_link');
- return (
- <SimpleModal
- header={header}
- onClose={this.props.onClose}
- onSubmit={this.handleSubmit}
- size="small"
- >
- {({ onCloseClick, onFormSubmit, submitting }) => (
- <form onSubmit={onFormSubmit}>
- <header className="modal-head">
- <h2>{header}</h2>
- </header>
-
- <div className="modal-body">
- <MandatoryFieldsExplanation className="modal-field" />
+ const formBody = (
+ <form id={FORM_ID} onSubmit={this.handleSubmit} className="sw-mb-2">
+ <MandatoryFieldsExplanation />
- <div className="modal-field">
- <label htmlFor="create-link-name">
- {translate('project_links.name')}
- <MandatoryFieldMarker />
- </label>
- <input
- autoFocus
- id="create-link-name"
- maxLength={128}
- name="name"
- onChange={this.handleNameChange}
- required
- type="text"
- value={this.state.name}
- />
- </div>
+ <FormField
+ label={translate('project_links.name')}
+ htmlFor="create-link-name"
+ className="sw-mt-4"
+ required
+ >
+ <InputField
+ autoFocus
+ required
+ size="auto"
+ id="create-link-name"
+ maxLength={128}
+ name="name"
+ onChange={this.handleNameChange}
+ type="text"
+ value={this.state.name}
+ />
+ </FormField>
- <div className="modal-field">
- <label htmlFor="create-link-url">
- {translate('project_links.url')}
- <MandatoryFieldMarker />
- </label>
- <input
- id="create-link-url"
- maxLength={128}
- name="url"
- onChange={this.handleUrlChange}
- required
- type="text"
- value={this.state.url}
- />
- </div>
- </div>
+ <FormField label={translate('project_links.url')} htmlFor="create-link-url" required>
+ <InputField
+ size="auto"
+ required
+ id="create-link-url"
+ maxLength={128}
+ name="url"
+ onChange={this.handleUrlChange}
+ type="text"
+ value={this.state.url}
+ />
+ </FormField>
+ </form>
+ );
- <footer className="modal-foot">
- <Spinner className="spacer-right" loading={submitting} />
- <SubmitButton disabled={submitting} id="create-link-confirm">
- {translate('create')}
- </SubmitButton>
- <ResetButtonLink disabled={submitting} onClick={onCloseClick}>
- {translate('cancel')}
- </ResetButtonLink>
- </footer>
- </form>
- )}
- </SimpleModal>
+ return (
+ <Modal
+ headerTitle={header}
+ onClose={this.props.onClose}
+ body={formBody}
+ primaryButton={
+ <ButtonPrimary form={FORM_ID} type="submit">
+ {translate('create')}
+ </ButtonPrimary>
+ }
+ />
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx b/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx
index 141ed03726d..a8729cf784f 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/Header.tsx
@@ -17,8 +17,9 @@
* 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, Title } from 'design-system';
import * as React from 'react';
-import { Button } from '../../components/controls/buttons';
import { translate } from '../../helpers/l10n';
import CreationModal from './CreationModal';
@@ -55,14 +56,14 @@ export default class Header extends React.PureComponent<Props, State> {
render() {
return (
<>
- <header className="page-header">
- <h1 className="page-title">{translate('project_links.page')}</h1>
- <div className="page-actions">
- <Button id="create-project-link" onClick={this.handleCreateClick}>
+ <header className="sw-mt-8 sw-mb-4">
+ <div className="sw-flex sw-justify-between">
+ <Title className="sw-mb-4">{translate('project_links.page')}</Title>
+ <ButtonPrimary id="create-project-link" onClick={this.handleCreateClick}>
{translate('create')}
- </Button>
+ </ButtonPrimary>
</div>
- <div className="page-description">{translate('project_links.page.description')}</div>
+ <p>{translate('project_links.page.description')}</p>
</header>
{this.state.creationModal && (
<CreationModal onClose={this.handleCreationModalClose} onSubmit={this.props.onCreate} />
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkRow.tsx
index 056d03b166a..f676e1fcce5 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkRow.tsx
@@ -17,12 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import {
+ ActionCell,
+ ContentCell,
+ DestructiveIcon,
+ Link,
+ Note,
+ TableRow,
+ TrashIcon,
+} from 'design-system';
import * as React from 'react';
import isValidUri from '../../app/utils/isValidUri';
-import Link from '../../components/common/Link';
-import { Button } from '../../components/controls/buttons';
import ConfirmButton from '../../components/controls/ConfirmButton';
-import ProjectLinkIcon from '../../components/icons/ProjectLinkIcon';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { getLinkName, isProvided } from '../../helpers/projectLinks';
import { ProjectLink } from '../../types/types';
@@ -35,28 +42,13 @@ interface Props {
export default class LinkRow extends React.PureComponent<Props> {
renderNameForProvided = (link: ProjectLink) => {
return (
- <div className="display-inline-block text-top">
+ <div>
<div>
- <span className="js-name">{getLinkName(link)}</span>
- </div>
- <div className="note little-spacer-top">
- <span className="js-type">{`sonar.links.${link.type}`}</span>
+ <span>{getLinkName(link)}</span>
</div>
- </div>
- );
- };
-
- renderName = (link: ProjectLink) => {
- return (
- <div>
- <ProjectLinkIcon className="little-spacer-right" type={link.type} />
- {isProvided(link) ? (
- this.renderNameForProvided(link)
- ) : (
- <div className="display-inline-block text-top">
- <span className="js-name">{link.name}</span>
- </div>
- )}
+ <Note className="sw-mt-1">
+ <span>{`sonar.links.${link.type}`}</span>
+ </Note>
</div>
);
};
@@ -79,13 +71,12 @@ export default class LinkRow extends React.PureComponent<Props> {
onConfirm={this.props.onDelete}
>
{({ onClick }) => (
- <Button
- className="button-red js-delete-button"
+ <DestructiveIcon
+ Icon={TrashIcon}
aria-label={translateWithParameters('project_links.delete_x_link', link.name ?? '')}
onClick={onClick}
- >
- {translate('delete')}
- </Button>
+ size="small"
+ />
)}
</ConfirmButton>
);
@@ -95,9 +86,17 @@ export default class LinkRow extends React.PureComponent<Props> {
const { link } = this.props;
return (
- <tr data-name={link.name}>
- <td className="nowrap">{this.renderName(link)}</td>
- <td className="nowrap js-url">
+ <TableRow data-name={link.name}>
+ <ContentCell>
+ {isProvided(link) ? (
+ this.renderNameForProvided(link)
+ ) : (
+ <div>
+ <span>{link.name}</span>
+ </div>
+ )}
+ </ContentCell>
+ <ContentCell>
{isValidUri(link.url) ? (
<Link to={link.url} target="_blank">
{link.url}
@@ -105,9 +104,9 @@ export default class LinkRow extends React.PureComponent<Props> {
) : (
link.url
)}
- </td>
- <td className="thin nowrap">{this.renderDeleteButton(link)}</td>
- </tr>
+ </ContentCell>
+ <ActionCell>{this.renderDeleteButton(link)}</ActionCell>
+ </TableRow>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/Table.tsx b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkTable.tsx
index 32807d0e219..9974c6c9964 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/Table.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinkTable.tsx
@@ -17,49 +17,41 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { ActionCell, ContentCell, Note, Table, TableRow } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
import { orderLinks } from '../../helpers/projectLinks';
import { ProjectLink } from '../../types/types';
-import LinkRow from './LinkRow';
+import LinkRow from './ProjectLinkRow';
interface Props {
links: ProjectLink[];
onDelete: (linkId: string) => Promise<void>;
}
-export default class Table extends React.PureComponent<Props> {
- renderHeader() {
- // keep empty cell for actions
- return (
- <thead>
- <tr>
- <th className="nowrap">{translate('project_links.name')}</th>
- <th className="nowrap width-100">{translate('project_links.url')}</th>
- <th className="thin">&nbsp;</th>
- </tr>
- </thead>
- );
+export default function ProjectLinkTable({ links, onDelete }: Readonly<Props>) {
+ if (!links.length) {
+ return <Note>{translate('project_links.no_results')}</Note>;
}
- render() {
- if (!this.props.links.length) {
- return <div className="note">{translate('no_results')}</div>;
- }
+ const orderedLinks = orderLinks(links);
- const orderedLinks = orderLinks(this.props.links);
+ const linkRows = orderedLinks.map((link) => (
+ <LinkRow key={link.id} link={link} onDelete={onDelete} />
+ ));
- const linkRows = orderedLinks.map((link) => (
- <LinkRow key={link.id} link={link} onDelete={this.props.onDelete} />
- ));
+ const header = (
+ <TableRow>
+ <ContentCell>{translate('project_links.name')}</ContentCell>
+ <ContentCell>{translate('project_links.url')}</ContentCell>
+ <ActionCell>&nbsp;</ActionCell>
+ </TableRow>
+ );
- return (
- <div className="boxed-group boxed-group-inner">
- <table className="data zebra" id="project-links">
- {this.renderHeader()}
- <tbody>{linkRows}</tbody>
- </table>
- </div>
- );
- }
+ return (
+ <Table columnCount={3} header={header}>
+ {linkRows}
+ </Table>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx
index 9ab0eddd0ed..5165a7b37bb 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx
@@ -17,15 +17,16 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { LargeCenteredLayout, PageContentFontWrapper, Spinner } from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { createLink, deleteLink, getProjectLinks } from '../../api/projectLinks';
import withComponentContext from '../../app/components/componentContext/withComponentContext';
-import Spinner from '../../components/ui/Spinner';
import { translate } from '../../helpers/l10n';
import { Component, ProjectLink } from '../../types/types';
import Header from './Header';
-import Table from './Table';
+import ProjectLinkTable from './ProjectLinkTable';
interface Props {
component: Component;
@@ -102,13 +103,15 @@ export class ProjectLinksApp extends React.PureComponent<Props, State> {
render() {
const { loading, links } = this.state;
return (
- <div className="page page-limited">
- <Helmet defer={false} title={translate('project_links.page')} />
- <Header onCreate={this.handleCreateLink} />
- <Spinner loading={loading}>
- {links && <Table links={links} onDelete={this.handleDeleteLink} />}
- </Spinner>
- </div>
+ <LargeCenteredLayout>
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Helmet defer={false} title={translate('project_links.page')} />
+ <Header onCreate={this.handleCreateLink} />
+ <Spinner loading={loading}>
+ <ProjectLinkTable links={links ?? []} onDelete={this.handleDeleteLink} />
+ </Spinner>
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx
index 932f16b3b7b..77051abf2f0 100644
--- a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx
@@ -72,7 +72,7 @@ function getPageObjects() {
const ui = {
pageTitle: byRole('heading', { name: 'project_links.page' }),
- noResultsTable: byText('no_results'),
+ noResultsTable: byText('project_links.no_results'),
createLinkButton: byRole('button', { name: 'create' }),
nameInput: byRole('textbox', { name: /project_links.name/ }),
urlInput: byRole('textbox', { name: /project_links.url/ }),
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index fe1d5567495..1568ee6a866 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -510,6 +510,7 @@ project_links.are_you_sure_to_delete_x_link=Are you sure you want to delete the
project_links.delete_x_link=Delete "{0}" link
project_links.name=Name
project_links.url=URL
+project_links.no_results=No links yet. Click "Create" to add one.
#------------------------------------------------------------------------------