diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2023-11-01 08:16:34 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-11-02 20:02:41 +0000 |
commit | d7128452ed937748f7a5b8dd618d38cd17fd129b (patch) | |
tree | 5753cb5b31db51a6ac9099257ead7df0a488b356 | |
parent | f4e45ecfb6e09a1006ba3f0acddb02c8026f4028 (diff) | |
download | sonarqube-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.tsx | 1 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/projectLinks/CreationModal.tsx | 115 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/projectLinks/Header.tsx | 15 | ||||
-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.tsx | 21 | ||||
-rw-r--r-- | server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx | 2 | ||||
-rw-r--r-- | sonar-core/src/main/resources/org/sonar/l10n/core.properties | 1 |
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"> </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> </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. #------------------------------------------------------------------------------ |