placeholder?: boolean;
}
+/** @deprecated Use Spinner from Echoes instead.
+ *
+ * Some of the props have changed or been renamed:
+ * - ~`customSpinner`~ has been removed
+ * - `loading` is now `isLoading`
+ * - `placeholder` is now `hasPlaceholder`
+ */
export function Spinner(props: React.PropsWithChildren<Props>) {
const intl = useIntl();
const {
* 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 { Link, Spinner } from '@sonarsource/echoes-react';
import { formatDistance } from 'date-fns';
-import { CheckIcon, FlagMessage, FlagWarningIcon, Link, Spinner, themeColor } from 'design-system';
+import { CheckIcon, FlagMessage, FlagWarningIcon, themeColor } from 'design-system';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { TaskStatuses } from '../../types/tasks';
interface SynchronisationWarningProps {
- short?: boolean;
data: AlmSyncStatus;
+ short?: boolean;
}
interface LastSyncProps {
- short?: boolean;
info: AlmSyncStatus['lastSync'];
+ short?: boolean;
}
function LastSyncAlert({ info, short }: Readonly<LastSyncProps>) {
if (info === undefined) {
return null;
}
+
const { finishedAt, errorMessage, status, summary, warningMessage } = info;
const formattedDate = finishedAt ? formatDistance(new Date(finishedAt), new Date()) : '';
<CheckIcon width={32} height={32} className="sw-mr-2" />
)}
</IconWrapper>
+
<i>
{warningMessage ? (
<FormattedMessage
- id="settings.authentication.github.synchronization_successful.with_warning"
defaultMessage={translate(
'settings.authentication.github.synchronization_successful.with_warning',
)}
+ id="settings.authentication.github.synchronization_successful.with_warning"
values={{
date: formattedDate,
details: (
<FlagMessage variant="error">
<div>
<FormattedMessage
- id="settings.authentication.github.synchronization_failed_short"
defaultMessage={translate(
'settings.authentication.github.synchronization_failed_short',
)}
+ id="settings.authentication.github.synchronization_failed_short"
values={{
details: (
<Link className="sw-ml-2" to="/admin/settings?category=authentication&tab=github">
return (
<>
<FlagMessage
- variant={status === TaskStatuses.Success ? 'success' : 'error'}
- role="alert"
aria-live="assertive"
+ role="alert"
+ variant={status === TaskStatuses.Success ? 'success' : 'error'}
>
<div>
{status === TaskStatuses.Success ? (
'settings.authentication.github.synchronization_successful',
formattedDate,
)}
+
<br />
+
{summary ?? ''}
</>
) : (
formattedDate,
)}
</div>
+
<br />
+
{errorMessage ?? ''}
</React.Fragment>
)}
</div>
</FlagMessage>
+
<FlagMessage variant="warning" role="alert" aria-live="assertive">
{warningMessage}
</FlagMessage>
}
export default function AlmSynchronisationWarning({
- short,
data,
+ short,
}: Readonly<SynchronisationWarningProps>) {
const loadingLabel =
data.nextSync &&
? 'settings.authentication.github.synchronization_pending'
: 'settings.authentication.github.synchronization_in_progress',
);
+
return (
<>
{!short && (
<div className={data.nextSync ? 'sw-flex sw-gap-2 sw-mb-4' : ''}>
- <Spinner loading={!!data.nextSync} ariaLabel={loadingLabel} />
+ <Spinner ariaLabel={loadingLabel} isLoading={!!data.nextSync} />
+
<div>{data.nextSync && loadingLabel}</div>
</div>
)}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { Spinner } from '@sonarsource/echoes-react';
import classNames from 'classnames';
-import { ItemButton, Spinner } from 'design-system';
+import { ItemButton } from 'design-system';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
event.preventDefault();
event.stopPropagation();
event.currentTarget.blur();
- if (qualifier) {
+
+ if (qualifier !== '') {
this.props.onMoreClick(qualifier);
}
};
handleMouseEnter = (qualifier: string) => {
- if (qualifier) {
+ if (qualifier !== '') {
this.props.onSelect(`qualifier###${qualifier}`);
}
};
this.handleMouseEnter(qualifier);
}}
>
- <Spinner loading={loadingMore === qualifier}>{translate('show_more')}</Spinner>
+ <Spinner isLoading={loadingMore === qualifier}>{translate('show_more')}</Spinner>
</ItemButton>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { FlagMessage, Spinner } from 'design-system';
+
+import { Spinner } from '@sonarsource/echoes-react';
+import { FlagMessage } from 'design-system';
import { findLastIndex, keyBy } from 'lodash';
import * as React from 'react';
import { getComponentForSourceViewer, getDuplications, getSources } from '../../../api/components';
* 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 { Spinner } from '@sonarsource/echoes-react';
import {
LAYOUT_FOOTER_HEIGHT,
LargeCenteredLayout,
PageContentFontWrapper,
- Spinner,
themeBorder,
themeColor,
} from 'design-system';
import { translate } from '../../../helpers/l10n';
import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages';
import { get, save } from '../../../helpers/storage';
+import { isDefined } from '../../../helpers/types';
import { AppState } from '../../../types/appstate';
import { ComponentQualifier } from '../../../types/component';
+import { MetricKey } from '../../../types/metrics';
import { RawQuery } from '../../../types/types';
import { CurrentUser, isLoggedIn } from '../../../types/users';
import { Query, hasFilterParams, parseUrlQuery } from '../query';
import ProjectsList from './ProjectsList';
interface Props {
+ appState: AppState;
currentUser: CurrentUser;
isFavorite: boolean;
location: Location;
- appState: AppState;
router: Router;
}
handlePerspectiveChange = ({ view }: { view?: string }) => {
const query: {
view: string | undefined;
- sort?: string | undefined;
+ sort?: string;
} = {
view: view === 'overall' ? undefined : view,
};
if (this.state.query.view === 'leak' || view === 'leak') {
- if (this.state.query.sort) {
+ if (isDefined(this.state.query.sort)) {
const sort = parseSorting(this.state.query.sort);
- if (SORTING_SWITCH[sort.sortValue]) {
+ if (isDefined(SORTING_SWITCH[sort.sortValue])) {
query.sort = (sort.sortDesc ? '-' : '') + SORTING_SWITCH[sort.sortValue];
}
}
+
this.props.router.push({ pathname: this.props.location.pathname, query });
} else {
this.updateLocationQuery(query);
return searchProjects(data).then(({ facets }) => {
const values = facets.find((facet) => facet.property === property)?.values ?? [];
+
return mapValues(keyBy(values, 'val'), 'count');
});
};
handleFavorite={this.handleFavorite}
isFavorite={this.props.isFavorite}
isFiltered={hasFilterParams(this.state.query)}
+ loading={this.state.loading}
+ loadMore={this.fetchMoreProjects}
projects={this.state.projects}
query={this.state.query}
- loadMore={this.fetchMoreProjects}
- loading={this.state.loading}
total={this.state.total}
/>
)}
render() {
return (
<StyledWrapper id="projects-page">
- <Suggestions suggestions="projects" />
+ <Suggestions suggestions={MetricKey.projects} />
<Helmet defer={false} title={translate('projects.page')} />
<h1 className="sw-sr-only">{translate('projects.page')}</h1>
view?: string;
} = {};
- if (get(LS_PROJECTS_SORT)) {
- options.sort = get(LS_PROJECTS_SORT) || undefined;
+ if (get(LS_PROJECTS_SORT) !== null) {
+ options.sort = get(LS_PROJECTS_SORT) ?? undefined;
}
- if (get(LS_PROJECTS_VIEW)) {
- options.view = get(LS_PROJECTS_VIEW) || undefined;
+ if (get(LS_PROJECTS_VIEW) !== null) {
+ options.view = get(LS_PROJECTS_VIEW) ?? undefined;
}
return options;
}
-function SetSearchParamsWrapper(props: Props) {
+function SetSearchParamsWrapper(props: Readonly<Props>) {
const [searchParams, setSearchParams] = useSearchParams();
const savedOptions = getStorageOptions();
React.useEffect(
() => {
- const hasViewParams = searchParams.get('sort') || searchParams.get('view');
- const hasSavedOptions = savedOptions.sort || savedOptions.view;
+ const hasViewParams = searchParams.get('sort') ?? searchParams.get('view');
+ const hasSavedOptions = savedOptions.sort ?? savedOptions.view;
- if (!hasViewParams && hasSavedOptions) {
+ if (!isDefined(hasViewParams) && isDefined(hasSavedOptions)) {
setSearchParams(savedOptions);
}
},
* 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, FlagMessage, Modal, Spinner } from 'design-system';
+
+import { Spinner } from '@sonarsource/echoes-react';
+import { ButtonPrimary, FlagMessage, Modal } from 'design-system';
import { keyBy } from 'lodash';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { isAllowToSignUpEnabled, isOrganizationListEmpty } from './hook/useGithubConfiguration';
interface Props {
- create: boolean;
- loading: boolean;
- values: Dict<SettingValue>;
- setNewValue: (key: string, value: string | boolean) => void;
canBeSave: boolean;
- onClose: () => void;
- tab: AuthenticationTabs;
+ create: boolean;
excludedField: string[];
hasLegacyConfiguration?: boolean;
+ loading: boolean;
+ onClose: () => void;
provisioningStatus?: ProvisioningType;
+ setNewValue: (key: string, value: string | boolean) => void;
+ tab: AuthenticationTabs;
+ values: Dict<SettingValue>;
}
interface ErrorValue {
export default function ConfigurationForm(props: Readonly<Props>) {
const {
- create,
- loading,
- values,
- setNewValue,
canBeSave,
- tab,
+ create,
excludedField,
hasLegacyConfiguration,
+ loading,
provisioningStatus,
+ setNewValue,
+ tab,
+ values,
} = props;
const [errors, setErrors] = React.useState<Dict<ErrorValue>>({});
const [showConfirmModal, setShowConfirmModal] = React.useState(false);
const errors = Object.values(values)
.filter((v) => v.newValue === undefined && v.value === undefined && v.mandatory)
.map((v) => ({ key: v.key, message: translate('field_required') }));
+
setErrors(keyBy(errors, 'key'));
}
};
const onSave = async () => {
const data = await changeConfig(Object.values(values));
+
const errors = data
.filter(({ success }) => !success)
.map(({ key }) => ({ key, message: translate('default_save_field_error_message') }));
const formBody = (
<form id={FORM_ID} onSubmit={handleSubmit}>
- <Spinner loading={loading} ariaLabel={translate('settings.authentication.form.loading')}>
+ <Spinner ariaLabel={translate('settings.authentication.form.loading')} isLoading={loading}>
<FlagMessage
className="sw-w-full sw-mb-8"
variant={hasLegacyConfiguration ? 'warning' : 'info'}
>
<span>
<FormattedMessage
- id={`settings.authentication.${helpMessage}`}
defaultMessage={translate(`settings.authentication.${helpMessage}`)}
+ id={`settings.authentication.${helpMessage}`}
values={{
link: (
<DocumentationLink
/>
</span>
</FlagMessage>
+
{Object.values(values).map((val) => {
if (excludedField.includes(val.key)) {
return null;
}
const isSet = hasLegacyConfiguration ? false : !val.isNotSet;
+
return (
<div key={val.key} className="sw-mb-8">
<AuthenticationFormField
- settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
definition={val.definition}
+ error={errors[val.key]?.message}
+ isNotSet={!isSet}
mandatory={val.mandatory}
onFieldChange={setNewValue}
- isNotSet={!isSet}
- error={errors[val.key]?.message}
+ settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
/>
</div>
);
return (
<>
<Modal
+ body={formBody}
headerTitle={header}
isScrollable
onClose={props.onClose}
- body={formBody}
primaryButton={
<ButtonPrimary form={FORM_ID} type="submit" autoFocus disabled={!canBeSave}>
{translate('settings.almintegration.form.save')}
- <Spinner className="sw-ml-2" loading={loading} />
+
+ <Spinner className="sw-ml-2" isLoading={loading} />
</ButtonPrimary>
}
/>
{showConfirmModal && (
<GitHubConfirmModal
- onConfirm={onSave}
onClose={() => setShowConfirmModal(false)}
- values={values}
+ onConfirm={onSave}
provisioningStatus={provisioningStatus ?? ProvisioningType.jit}
+ values={values}
/>
)}
</>