'/project/branches',
'/project/key',
'/project/deletion',
+ '/project/links',
];
export default function GlobalContainer() {
* 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 {
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);
};
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>
+ }
+ />
);
}
}
* 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';
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} />
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * 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';
-
-interface Props {
- link: ProjectLink;
- onDelete: (linkId: string) => Promise<void>;
-}
-
-export default class LinkRow extends React.PureComponent<Props> {
- renderNameForProvided = (link: ProjectLink) => {
- return (
- <div className="display-inline-block text-top">
- <div>
- <span className="js-name">{getLinkName(link)}</span>
- </div>
- <div className="note little-spacer-top">
- <span className="js-type">{`sonar.links.${link.type}`}</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>
- )}
- </div>
- );
- };
-
- renderDeleteButton = (link: ProjectLink) => {
- if (isProvided(link)) {
- return null;
- }
-
- return (
- <ConfirmButton
- confirmButtonText={translate('delete')}
- confirmData={link.id}
- isDestructive
- modalBody={translateWithParameters(
- 'project_links.are_you_sure_to_delete_x_link',
- link.name!,
- )}
- modalHeader={translate('project_links.delete_project_link')}
- onConfirm={this.props.onDelete}
- >
- {({ onClick }) => (
- <Button
- className="button-red js-delete-button"
- aria-label={translateWithParameters('project_links.delete_x_link', link.name ?? '')}
- onClick={onClick}
- >
- {translate('delete')}
- </Button>
- )}
- </ConfirmButton>
- );
- };
-
- render() {
- const { link } = this.props;
-
- return (
- <tr data-name={link.name}>
- <td className="nowrap">{this.renderName(link)}</td>
- <td className="nowrap js-url">
- {isValidUri(link.url) ? (
- <Link to={link.url} target="_blank">
- {link.url}
- </Link>
- ) : (
- link.url
- )}
- </td>
- <td className="thin nowrap">{this.renderDeleteButton(link)}</td>
- </tr>
- );
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import {
+ ActionCell,
+ ContentCell,
+ DestructiveIcon,
+ Link,
+ Note,
+ TableRow,
+ TrashIcon,
+} from 'design-system';
+import * as React from 'react';
+import isValidUri from '../../app/utils/isValidUri';
+import ConfirmButton from '../../components/controls/ConfirmButton';
+import { translate, translateWithParameters } from '../../helpers/l10n';
+import { getLinkName, isProvided } from '../../helpers/projectLinks';
+import { ProjectLink } from '../../types/types';
+
+interface Props {
+ link: ProjectLink;
+ onDelete: (linkId: string) => Promise<void>;
+}
+
+export default class LinkRow extends React.PureComponent<Props> {
+ renderNameForProvided = (link: ProjectLink) => {
+ return (
+ <div>
+ <div>
+ <span>{getLinkName(link)}</span>
+ </div>
+ <Note className="sw-mt-1">
+ <span>{`sonar.links.${link.type}`}</span>
+ </Note>
+ </div>
+ );
+ };
+
+ renderDeleteButton = (link: ProjectLink) => {
+ if (isProvided(link)) {
+ return null;
+ }
+
+ return (
+ <ConfirmButton
+ confirmButtonText={translate('delete')}
+ confirmData={link.id}
+ isDestructive
+ modalBody={translateWithParameters(
+ 'project_links.are_you_sure_to_delete_x_link',
+ link.name!,
+ )}
+ modalHeader={translate('project_links.delete_project_link')}
+ onConfirm={this.props.onDelete}
+ >
+ {({ onClick }) => (
+ <DestructiveIcon
+ Icon={TrashIcon}
+ aria-label={translateWithParameters('project_links.delete_x_link', link.name ?? '')}
+ onClick={onClick}
+ size="small"
+ />
+ )}
+ </ConfirmButton>
+ );
+ };
+
+ render() {
+ const { link } = this.props;
+
+ return (
+ <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}
+ </Link>
+ ) : (
+ link.url
+ )}
+ </ContentCell>
+ <ActionCell>{this.renderDeleteButton(link)}</ActionCell>
+ </TableRow>
+ );
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+import { 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 './ProjectLinkRow';
+
+interface Props {
+ links: ProjectLink[];
+ onDelete: (linkId: string) => Promise<void>;
+}
+
+export default function ProjectLinkTable({ links, onDelete }: Readonly<Props>) {
+ if (!links.length) {
+ return <Note>{translate('project_links.no_results')}</Note>;
+ }
+
+ const orderedLinks = orderLinks(links);
+
+ const linkRows = orderedLinks.map((link) => (
+ <LinkRow key={link.id} link={link} onDelete={onDelete} />
+ ));
+
+ const header = (
+ <TableRow>
+ <ContentCell>{translate('project_links.name')}</ContentCell>
+ <ContentCell>{translate('project_links.url')}</ContentCell>
+ <ActionCell> </ActionCell>
+ </TableRow>
+ );
+
+ return (
+ <Table columnCount={3} header={header}>
+ {linkRows}
+ </Table>
+ );
+}
* 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;
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>
);
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-import * as React from 'react';
-import { translate } from '../../helpers/l10n';
-import { orderLinks } from '../../helpers/projectLinks';
-import { ProjectLink } from '../../types/types';
-import LinkRow from './LinkRow';
-
-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>
- );
- }
-
- render() {
- if (!this.props.links.length) {
- return <div className="note">{translate('no_results')}</div>;
- }
-
- const orderedLinks = orderLinks(this.props.links);
-
- const linkRows = orderedLinks.map((link) => (
- <LinkRow key={link.id} link={link} onDelete={this.props.onDelete} />
- ));
-
- return (
- <div className="boxed-group boxed-group-inner">
- <table className="data zebra" id="project-links">
- {this.renderHeader()}
- <tbody>{linkRows}</tbody>
- </table>
- </div>
- );
- }
-}
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/ }),
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.
#------------------------------------------------------------------------------