aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>2023-03-13 12:35:13 +0100
committersonartech <sonartech@sonarsource.com>2023-03-14 20:03:27 +0000
commitc4e2a351504a92709154992ab1c2963201538d4c (patch)
treec88f89688a387b6d24cdd13230429a821d64033f /server/sonar-web/src/main/js/apps
parentdd791e68ca7a5b0375e7cba70a2f8f27d4c75c26 (diff)
downloadsonarqube-c4e2a351504a92709154992ab1c2963201538d4c.tar.gz
sonarqube-c4e2a351504a92709154992ab1c2963201538d4c.zip
Fix some code smells in MMF-3035
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/routes.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts44
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx65
-rw-r--r--server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap31
-rw-r--r--server/sonar-web/src/main/js/apps/projects/utils.ts51
-rw-r--r--server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx3
10 files changed, 181 insertions, 49 deletions
diff --git a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
index 99ff1c64e91..34b3b2c16cc 100644
--- a/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
+++ b/server/sonar-web/src/main/js/apps/account/profile/UserExternalIdentity.tsx
@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { getTextColor } from 'design-system';
import * as React from 'react';
import { getIdentityProviders } from '../../../api/users';
import { colors } from '../../../app/theme';
-import { getTextColor } from '../../../helpers/colors';
import { getBaseUrl } from '../../../helpers/system';
import { IdentityProvider } from '../../../types/types';
import { LoggedInUser } from '../../../types/users';
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
index d74f0b6bd07..7cb089ccf76 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx
@@ -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 * as React from 'react';
import Link from '../../../components/common/Link';
import { ButtonLink } from '../../../components/controls/buttons';
@@ -217,7 +218,7 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> {
const EXTERNAL_PREFIX = 'external_';
const { ruleDetails } = this.props;
const displayedKey = ruleDetails.key.startsWith(EXTERNAL_PREFIX)
- ? ruleDetails.key.substr(EXTERNAL_PREFIX.length)
+ ? ruleDetails.key.substring(EXTERNAL_PREFIX.length)
: ruleDetails.key;
return <span className="note text-middle">{displayedKey}</span>;
}
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
index 5ba7f224b13..5a9221be36b 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
+++ b/server/sonar-web/src/main/js/apps/coding-rules/routes.tsx
@@ -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 React, { useEffect } from 'react';
import { Route, useLocation, useNavigate } from 'react-router-dom';
import { RawQuery } from '../../types/types';
@@ -44,7 +45,7 @@ function HashEditWrapper() {
useEffect(() => {
const { hash } = location;
if (hash.length > 1) {
- const query = parseHash(hash.substr(1));
+ const query = parseHash(hash.substring(1));
const normalizedQuery = {
...serializeQuery(parseQuery(query)),
open: query.open,
diff --git a/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx b/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
index 78107922aac..a1fe1c5d19e 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/ListItem.tsx
@@ -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 * as React from 'react';
import Issue from '../../../components/issue/Issue';
import { BranchLike } from '../../../types/branch-like';
@@ -59,7 +60,7 @@ export default class ListItem extends React.PureComponent<Props> {
const issuesReset = { issues: [] };
if (property.startsWith('tag###')) {
- const tag = property.substr(6);
+ const tag = property.substring('tag###'.length);
onFilterChange({ ...issuesReset, tags: [tag] });
} else {
switch (property) {
diff --git a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
index b6c7496acb2..230acd91907 100644
--- a/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
+++ b/server/sonar-web/src/main/js/apps/projects/__tests__/utils-test.ts
@@ -17,8 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { searchProjects } from '../../../api/components';
import { mockComponent } from '../../../helpers/mocks/component';
+import { Component } from '../../../types/types';
import * as utils from '../utils';
jest.mock('../../../api/components', () => ({
@@ -55,11 +57,13 @@ describe('parseSorting', () => {
});
describe('formatDuration', () => {
- const ONE_MINUTE = 60000;
+ const ONE_SECOND = 1000;
+ const ONE_MINUTE = 60 * ONE_SECOND;
const ONE_HOUR = 60 * ONE_MINUTE;
const ONE_DAY = 24 * ONE_HOUR;
const ONE_MONTH = 30 * ONE_DAY;
const ONE_YEAR = 12 * ONE_MONTH;
+
it('render years and months only', () => {
expect(utils.formatDuration(ONE_YEAR * 4 + ONE_MONTH * 2 + ONE_DAY * 10)).toEqual(
'duration.years.4 duration.months.2 '
@@ -81,13 +85,14 @@ describe('formatDuration', () => {
});
it('render less than a minute', () => {
- expect(utils.formatDuration(1000)).toEqual('duration.seconds');
+ expect(utils.formatDuration(ONE_SECOND)).toEqual('duration.seconds');
});
});
describe('fetchProjects', () => {
it('correctly converts the passed arguments to the desired query format', async () => {
- await utils.fetchProjects({}, true);
+ await utils.fetchProjects({ isFavorite: true, query: {} });
+
expect(searchProjects).toHaveBeenCalledWith({
f: 'analysisDate,leakPeriodDate',
facets: utils.FACETS.join(),
@@ -96,7 +101,8 @@ describe('fetchProjects', () => {
ps: 50,
});
- await utils.fetchProjects({ view: 'leak' }, false, 3);
+ await utils.fetchProjects({ isFavorite: false, pageIndex: 3, query: { view: 'leak' } });
+
expect(searchProjects).toHaveBeenCalledWith({
f: 'analysisDate,leakPeriodDate',
facets: utils.LEAK_FACETS.join(),
@@ -107,6 +113,7 @@ describe('fetchProjects', () => {
it('correctly treats result data', async () => {
const components = [mockComponent({ key: 'foo' }), mockComponent({ key: 'bar' })];
+
(searchProjects as jest.Mock).mockResolvedValue({
components,
facets: [
@@ -121,20 +128,25 @@ describe('fetchProjects', () => {
],
paging: { total: 2 },
});
- await utils.fetchProjects({}, true).then((r) => {
+
+ await utils.fetchProjects({ isFavorite: true, query: {} }).then((r) => {
expect(r).toEqual({
facets: {
new_coverage: { NO_DATA: 0 },
languages: { css: 10, js: 2 },
},
- projects: components.map((component: any) => {
- if (component.key === 'foo') {
- component.measures = { new_coverage: '10' };
- } else {
- component.measures = { languages: '20' };
+ projects: components.map(
+ (component: Component & { measures: { languages?: string; new_coverage?: string } }) => {
+ // eslint-disable-next-line jest/no-conditional-in-test
+ if (component.key === 'foo') {
+ component.measures = { new_coverage: '10' };
+ } else {
+ component.measures = { languages: '20' };
+ }
+
+ return component;
}
- return component;
- }),
+ ),
total: 2,
});
});
@@ -148,3 +160,11 @@ describe('defineMetrics', () => {
expect(utils.defineMetrics({})).toBe(utils.METRICS);
});
});
+
+describe('convertToSorting', () => {
+ it('handles asc and desc sort', () => {
+ expect(utils.convertToSorting({ sort: '-size' })).toStrictEqual({ asc: false, s: 'ncloc' });
+ expect(utils.convertToSorting({})).toStrictEqual({ s: undefined });
+ expect(utils.convertToSorting({ sort: 'search' })).toStrictEqual({ s: 'query' });
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
index 55aa17d9838..536f90a44cb 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx
@@ -81,7 +81,9 @@ export class AllProjects extends React.PureComponent<Props, State> {
handleRequiredAuthentication();
return;
}
+
this.handleQueryChange();
+
addSideBarClass();
}
@@ -97,8 +99,11 @@ export class AllProjects extends React.PureComponent<Props, State> {
}
fetchProjects = (query: Query) => {
+ const { isFavorite } = this.props;
+
this.setState({ loading: true, query });
- fetchProjects(query, this.props.isFavorite).then((response) => {
+
+ fetchProjects({ isFavorite, query }).then((response) => {
if (this.mounted) {
this.setState({
facets: response.facets,
@@ -112,10 +117,13 @@ export class AllProjects extends React.PureComponent<Props, State> {
};
fetchMoreProjects = () => {
+ const { isFavorite } = this.props;
const { pageIndex, projects, query } = this.state;
- if (pageIndex && projects && query) {
+
+ if (pageIndex && projects && Object.keys(query).length !== 0) {
this.setState({ loading: true });
- fetchProjects(query, this.props.isFavorite, pageIndex + 1).then((response) => {
+
+ fetchProjects({ isFavorite, query, pageIndex: pageIndex + 1 }).then((response) => {
if (this.mounted) {
this.setState({
loading: false,
@@ -127,9 +135,9 @@ export class AllProjects extends React.PureComponent<Props, State> {
}
};
- getSort = () => this.state.query.sort || 'name';
+ getSort = () => this.state.query.sort ?? 'name';
- getView = () => this.state.query.view || 'overall';
+ getView = () => this.state.query.view ?? 'overall';
handleClearAll = () => {
this.props.router.push({ pathname: this.props.location.pathname });
@@ -147,7 +155,7 @@ export class AllProjects extends React.PureComponent<Props, State> {
});
};
- handlePerspectiveChange = ({ view }: { view: string }) => {
+ handlePerspectiveChange = ({ view }: { view?: string }) => {
const query: {
view: string | undefined;
sort?: string | undefined;
@@ -158,6 +166,7 @@ export class AllProjects extends React.PureComponent<Props, State> {
if (this.state.query.view === 'leak' || view === 'leak') {
if (this.state.query.sort) {
const sort = parseSorting(this.state.query.sort);
+
if (SORTING_SWITCH[sort.sortValue]) {
query.sort = (sort.sortDesc ? '-' : '') + SORTING_SWITCH[sort.sortValue];
}
@@ -306,12 +315,15 @@ function getStorageOptions() {
sort?: string;
view?: string;
} = {};
+
if (get(LS_PROJECTS_SORT)) {
options.sort = get(LS_PROJECTS_SORT) || undefined;
}
+
if (get(LS_PROJECTS_VIEW)) {
options.view = get(LS_PROJECTS_VIEW) || undefined;
}
+
return options;
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
index 9be34752c05..88c55dc28d0 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/AllProjects-test.tsx
@@ -76,8 +76,10 @@ it('renders', () => {
it('fetches projects', () => {
shallowRender();
- expect(fetchProjects).toHaveBeenLastCalledWith(
- {
+
+ expect(fetchProjects).toHaveBeenLastCalledWith({
+ isFavorite: false,
+ query: {
coverage: undefined,
duplications: undefined,
gate: undefined,
@@ -97,46 +99,58 @@ it('fetches projects', () => {
tags: undefined,
view: undefined,
},
- false
- );
+ });
});
it('changes sort', () => {
const push = jest.fn();
- const wrapper = shallowRender({}, push);
- wrapper.find('PageHeader').prop<Function>('onSortChange')('size', false);
+ const wrapper = shallowRender({ push });
+
+ wrapper.find('PageHeader').prop<(sort: string, desc: boolean) => void>('onSortChange')(
+ 'size',
+ false
+ );
+
expect(push).toHaveBeenLastCalledWith({ pathname: '/projects', query: { sort: 'size' } });
expect(save).toHaveBeenLastCalledWith(LS_PROJECTS_SORT, 'size');
});
it('changes perspective to leak', () => {
const push = jest.fn();
- const wrapper = shallowRender({}, push);
- wrapper.find('PageHeader').prop<Function>('onPerspectiveChange')({ view: 'leak' });
+ const wrapper = shallowRender({ push });
+
+ wrapper.find('PageHeader').prop<({ view }: { view?: string }) => void>('onPerspectiveChange')({
+ view: 'leak',
+ });
+
expect(push).toHaveBeenLastCalledWith({
pathname: '/projects',
query: { view: 'leak' },
});
+
expect(save).toHaveBeenCalledWith(LS_PROJECTS_SORT, undefined);
expect(save).toHaveBeenCalledWith(LS_PROJECTS_VIEW, 'leak');
});
it('updates sorting when changing perspective from leak', () => {
const push = jest.fn();
- const wrapper = shallowRender({}, push);
+ const wrapper = shallowRender({ push });
wrapper.setState({ query: { sort: 'new_coverage', view: 'leak' } });
- wrapper.find('PageHeader').prop<Function>('onPerspectiveChange')({
+
+ wrapper.find('PageHeader').prop<({ view }: { view?: string }) => void>('onPerspectiveChange')({
view: undefined,
});
+
expect(push).toHaveBeenLastCalledWith({
pathname: '/projects',
query: { sort: 'coverage', view: undefined },
});
+
expect(save).toHaveBeenCalledWith(LS_PROJECTS_SORT, 'coverage');
expect(save).toHaveBeenCalledWith(LS_PROJECTS_VIEW, undefined);
});
-it('handles favorite projects', () => {
+it('handles updating the favorite status of a project', () => {
const wrapper = shallowRender();
expect(wrapper.state('projects')).toMatchSnapshot();
@@ -144,11 +158,28 @@ it('handles favorite projects', () => {
expect(wrapper.state('projects')).toMatchSnapshot();
});
-function shallowRender(
- props: Partial<AllProjects['props']> = {},
- push = jest.fn(),
- replace = jest.fn()
-) {
+it('handles showing favorite projects on load', () => {
+ const wrapper = shallowRender({
+ props: { currentUser: { dismissedNotices: {}, isLoggedIn: false }, isFavorite: true },
+ });
+
+ expect(wrapper.state('projects')).toMatchSnapshot();
+
+ wrapper.instance().handleFavorite('foo', true);
+ expect(wrapper.state('projects')).toMatchSnapshot();
+});
+
+const defaults = { props: {}, push: () => undefined, replace: () => undefined };
+
+function shallowRender({
+ props = defaults.props,
+ push = defaults.push,
+ replace = defaults.replace,
+}: {
+ props?: Partial<AllProjects['props']>;
+ push?: () => void;
+ replace?: () => void;
+} = defaults) {
const wrapper = shallow<AllProjects>(
<AllProjects
currentUser={{ isLoggedIn: true, dismissedNotices: {} }}
@@ -161,6 +192,7 @@ function shallowRender(
{...props}
/>
);
+
wrapper.setState({
loading: false,
projects: [
@@ -175,5 +207,6 @@ function shallowRender(
],
total: 0,
});
+
return wrapper;
}
diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
index aefede3dc66..9bad4f35742 100644
--- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`handles favorite projects 1`] = `
+exports[`handles showing favorite projects on load 1`] = `
[
{
"key": "foo",
@@ -13,7 +13,34 @@ exports[`handles favorite projects 1`] = `
]
`;
-exports[`handles favorite projects 2`] = `
+exports[`handles showing favorite projects on load 2`] = `
+[
+ {
+ "isFavorite": true,
+ "key": "foo",
+ "measures": {},
+ "name": "Foo",
+ "qualifier": "TRK",
+ "tags": [],
+ "visibility": "public",
+ },
+]
+`;
+
+exports[`handles updating the favorite status of a project 1`] = `
+[
+ {
+ "key": "foo",
+ "measures": {},
+ "name": "Foo",
+ "qualifier": "TRK",
+ "tags": [],
+ "visibility": "public",
+ },
+]
+`;
+
+exports[`handles updating the favorite status of a project 2`] = `
[
{
"isFavorite": true,
diff --git a/server/sonar-web/src/main/js/apps/projects/utils.ts b/server/sonar-web/src/main/js/apps/projects/utils.ts
index 51a7afbe3ff..235018b7a9a 100644
--- a/server/sonar-web/src/main/js/apps/projects/utils.ts
+++ b/server/sonar-web/src/main/js/apps/projects/utils.ts
@@ -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 { invert } from 'lodash';
import { Facet, searchProjects } from '../../api/components';
import { getMeasuresForProjects } from '../../api/measures';
@@ -150,22 +151,33 @@ export const LEAK_FACETS = [
const REVERSED_FACETS = ['coverage', 'new_coverage'];
export function localizeSorting(sort?: string): string {
- return translate('projects.sort', sort || 'name');
+ return translate('projects.sort', sort ?? 'name');
}
export function parseSorting(sort: string): { sortValue: string; sortDesc: boolean } {
- const desc = sort[0] === '-';
- return { sortValue: desc ? sort.substr(1) : sort, sortDesc: desc };
+ const desc = sort.startsWith('-');
+
+ return { sortValue: desc ? sort.substring(1) : sort, sortDesc: desc };
}
-export function fetchProjects(query: Query, isFavorite: boolean, pageIndex = 1) {
+export function fetchProjects({
+ isFavorite,
+ query,
+ pageIndex = 1,
+}: {
+ query: Query;
+ isFavorite: boolean;
+ pageIndex?: number;
+}) {
const ps = PAGE_SIZE;
+
const data = convertToQueryData(query, isFavorite, {
p: pageIndex > 1 ? pageIndex : undefined,
ps,
facets: defineFacets(query).join(),
f: 'analysisDate,leakPeriodDate',
});
+
return searchProjects(data)
.then((response) =>
Promise.all([fetchProjectMeasures(response.components, query), Promise.resolve(response)])
@@ -183,6 +195,7 @@ export function fetchProjects(query: Query, isFavorite: boolean, pageIndex = 1)
componentMeasures[measure.metric] = value;
}
});
+
return { ...component, measures: componentMeasures };
}),
total: paging.total,
@@ -194,6 +207,7 @@ export function defineMetrics(query: Query): string[] {
if (query.view === 'leak') {
return LEAK_METRICS;
}
+
return METRICS;
}
@@ -201,6 +215,7 @@ function defineFacets(query: Query): string[] {
if (query.view === 'leak') {
return LEAK_FACETS;
}
+
return FACETS;
}
@@ -212,12 +227,15 @@ function convertToQueryData(query: Query, isFavorite: boolean, defaultData = {})
if (filter) {
data.filter = filter;
}
+
if (sort.s) {
data.s = sort.s;
}
+
if (sort.asc !== undefined) {
data.asc = sort.asc;
}
+
return data;
}
@@ -228,14 +246,17 @@ export function fetchProjectMeasures(projects: Array<{ key: string }>, query: Qu
const projectKeys = projects.map((project) => project.key);
const metrics = defineMetrics(query);
+
return getMeasuresForProjects(projectKeys, metrics);
}
function mapFacetValues(values: Array<{ val: string; count: number }>) {
const map: Dict<number> = {};
+
values.forEach((value) => {
map[value.val] = value.count;
});
+
return map;
}
@@ -266,22 +287,27 @@ const metricToPropertyMap = invert(propertyToMetricMap);
function getFacetsMap(facets: Facet[]) {
const map: Dict<Dict<number>> = {};
+
facets.forEach((facet) => {
const property = metricToPropertyMap[facet.property];
const { values } = facet;
+
if (REVERSED_FACETS.includes(property)) {
values.reverse();
}
+
map[property] = mapFacetValues(values);
});
+
return map;
}
-function convertToSorting({ sort }: Query): { s?: string; asc?: boolean } {
- if (sort && sort[0] === '-') {
- return { s: propertyToMetricMap[sort.substr(1)], asc: false };
+export function convertToSorting({ sort }: Query): { s?: string; asc?: boolean } {
+ if (sort?.startsWith('-')) {
+ return { s: propertyToMetricMap[sort.substring(1)], asc: false };
}
- return { s: propertyToMetricMap[sort || ''] };
+
+ return { s: propertyToMetricMap[sort ?? ''] };
}
const ONE_MINUTE = 60000;
@@ -294,15 +320,18 @@ function format(periods: Array<{ value: number; label: string }>) {
let result = '';
let count = 0;
let lastId = -1;
+
for (let i = 0; i < periods.length && count < 2; i++) {
if (periods[i].value > 0) {
count++;
+
if (lastId < 0 || lastId + 1 === i) {
lastId = i;
result += translateWithParameters(periods[i].label, periods[i].value) + ' ';
}
}
}
+
return result;
}
@@ -310,15 +339,21 @@ export function formatDuration(ms: number) {
if (ms < ONE_MINUTE) {
return translate('duration.seconds');
}
+
const years = Math.floor(ms / ONE_YEAR);
ms -= years * ONE_YEAR;
+
const months = Math.floor(ms / ONE_MONTH);
ms -= months * ONE_MONTH;
+
const days = Math.floor(ms / ONE_DAY);
ms -= days * ONE_DAY;
+
const hours = Math.floor(ms / ONE_HOUR);
ms -= hours * ONE_HOUR;
+
const minutes = Math.floor(ms / ONE_MINUTE);
+
return format([
{ value: years, label: 'duration.years' },
{ value: months, label: 'duration.months' },
diff --git a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
index df385e549ed..6d4d6872ce8 100644
--- a/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
+++ b/server/sonar-web/src/main/js/apps/users/components/UserListItemIdentity.tsx
@@ -17,9 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { getTextColor } from 'design-system';
import * as React from 'react';
import { colors } from '../../../app/theme';
-import { getTextColor } from '../../../helpers/colors';
import { getBaseUrl } from '../../../helpers/system';
import { IdentityProvider } from '../../../types/types';
import { User } from '../../../types/users';