* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import classNames from 'classnames';
import React from 'react';
import tw from 'twin.macro';
+import { OPACITY_20_PERCENT } from '../helpers/constants';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
import { isDefined } from '../helpers/types';
import { ThemedProps } from '../types/theme';
--color: ${({ currentColor, theme }) =>
currentColor ? 'currentColor' : themeContrast('interactiveIcon')({ theme })};
--colorHover: ${themeContrast('interactiveIconHover')};
- --focus: ${themeColor('interactiveIconFocus', 0.2)};
+ --focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
`;
export const DiscreetInteractiveIcon: React.FC<InteractiveIconProps> = styled(InteractiveIcon)`
--backgroundHover: ${themeColor('destructiveIconHover')};
--color: ${themeContrast('destructiveIcon')};
--colorHover: ${themeContrast('destructiveIconHover')};
- --focus: ${themeColor('destructiveIconFocus', 0.2)};
+ --focus: ${themeColor('destructiveIconFocus', OPACITY_20_PERCENT)};
`;
export const DismissProductNewsIcon: React.FC<InteractiveIconProps> = styled(InteractiveIcon)`
--backgroundHover: ${themeColor('productNewsHover')};
--color: ${themeContrast('productNews')};
--colorHover: ${themeContrast('productNewsHover')};
- --focus: ${themeColor('interactiveIconFocus', 0.2)};
+ --focus: ${themeColor('interactiveIconFocus', OPACITY_20_PERCENT)};
height: 28px;
`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import React from 'react';
import tw from 'twin.macro';
+import { OPACITY_20_PERCENT } from '../helpers/constants';
import { themeBorder, themeColor, themeContrast } from '../helpers/theme';
import { ThemedProps } from '../types/theme';
import { BaseLink, LinkProps } from './Link';
--background: ${themeColor('button')};
--backgroundHover: ${themeColor('buttonHover')};
--color: ${themeContrast('primary')};
- --focus: ${themeColor('button', 0.2)};
+ --focus: ${themeColor('button', OPACITY_20_PERCENT)};
--border: ${themeBorder('default', 'transparent')};
`;
--background: ${themeColor('buttonSecondary')};
--backgroundHover: ${themeColor('buttonSecondaryHover')};
--color: ${themeContrast('buttonSecondary')};
- --focus: ${themeColor('buttonSecondaryBorder', 0.2)};
+ --focus: ${themeColor('buttonSecondaryBorder', OPACITY_20_PERCENT)};
--border: ${themeBorder('default', 'buttonSecondaryBorder')};
`;
--background: ${themeColor('dangerButton')};
--backgroundHover: ${themeColor('dangerButtonHover')};
--color: ${themeContrast('dangerButton')};
- --focus: ${themeColor('dangerButtonFocus', 0.2)};
+ --focus: ${themeColor('dangerButtonFocus', OPACITY_20_PERCENT)};
--border: ${themeBorder('default', 'transparent')};
`;
--background: ${themeColor('dangerButtonSecondary')};
--backgroundHover: ${themeColor('dangerButtonSecondaryHover')};
--color: ${themeContrast('dangerButtonSecondary')};
- --focus: ${themeColor('dangerButtonSecondaryFocus', 0.2)};
+ --focus: ${themeColor('dangerButtonSecondaryFocus', OPACITY_20_PERCENT)};
--border: ${themeBorder('default', 'dangerButtonSecondaryBorder')};
`;
--background: ${themeColor('thirdPartyButton')};
--backgroundHover: ${themeColor('thirdPartyButtonHover')};
--color: ${themeContrast('thirdPartyButton')};
- --focus: ${themeColor('thirdPartyButtonBorder', 0.2)};
+ --focus: ${themeColor('thirdPartyButtonBorder', OPACITY_20_PERCENT)};
--border: ${themeBorder('default', 'thirdPartyButtonBorder')};
`;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { CSSColor } from '../types/theme';
/* eslint-disable no-bitwise, no-mixed-operators */
+
export function stringToColor(str: string) {
let hash = 0;
+
for (let i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
+
let color = '#';
+
for (let i = 0; i < 3; i++) {
const value = (hash >> (i * 8)) & 0xff;
- color += ('00' + value.toString(16)).substr(-2);
+ color += value.toString(16).padStart(2, '0');
}
+
return color;
}
export function isDarkColor(color: string) {
- color = color.substr(1);
+ color = color.substring(1);
+
if (color.length === 3) {
// shortcut notation: #f90
color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
}
- const rgb = parseInt(color.substr(1), 16);
+
+ const rgb = parseInt(color.substring(1), 16);
const r = (rgb >> 16) & 0xff;
const g = (rgb >> 8) & 0xff;
const b = (rgb >> 0) & 0xff;
const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+
return luma < 140;
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import { theme } from 'twin.macro';
export const DEFAULT_LOCALE = 'en';
export const CORE_CONCEPTS_WIDTH = 350;
export const DARK_THEME_ID = 'dark-theme';
+
+export const OPACITY_20_PERCENT = 0.2;
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+export * from './colors';
export * from './constants';
export * from './positioning';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import { OPACITY_20_PERCENT } from '../helpers/constants';
import COLORS from './colors';
const primary = {
radioHover: COLORS.indigo[50],
radioFocus: COLORS.indigo[50],
radioFocusBorder: COLORS.indigo[300],
- radioFocusOutline: [...COLORS.indigo[300], 0.2],
+ radioFocusOutline: [...COLORS.indigo[300], OPACITY_20_PERCENT],
radioChecked: COLORS.indigo[50],
radioDisabled: secondary.default,
radioDisabledBackground: secondary.light,
toggle: COLORS.white,
toggleBorder: secondary.default,
toggleHover: secondary.light,
- toggleFocus: [...secondary.default, 0.2],
+ toggleFocus: [...secondary.default, OPACITY_20_PERCENT],
// code snippet
codeSnippetBackground: COLORS.blueGrey[25],
interactiveIconHover: COLORS.indigo[50],
interactiveIconFocus: primary.default,
bannerIcon: 'transparent',
- bannerIconHover: [...COLORS.red[600], 0.2],
+ bannerIconHover: [...COLORS.red[600], OPACITY_20_PERCENT],
bannerIconFocus: danger.default,
discreetInteractiveIcon: secondary.dark,
destructiveIcon: 'transparent',
borders: {
default: ['1px', 'solid', ...COLORS.grey[50]],
active: ['3px', 'solid', ...primary.light],
- focus: ['4px', 'solid', ...secondary.default, 0.2],
+ focus: ['4px', 'solid', ...secondary.default, OPACITY_20_PERCENT],
},
avatar: {
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import {
DropdownMenu,
InputSearch,
import { KeyboardKeys } from '../../../helpers/keycodes';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { getKeyboardShortcutEnabled } from '../../../helpers/preferences';
-import { scrollToElement } from '../../../helpers/scrolling';
import { getComponentOverviewUrl } from '../../../helpers/urls';
import { ComponentQualifier } from '../../../types/component';
import { Dict } from '../../../types/types';
super(props);
this.nodes = {};
this.search = debounce(this.search, 250);
+
this.state = {
loading: false,
more: {},
if (!this.state.open && !this.state.query) {
this.search('');
}
+
this.setState({ open: true });
};
if (this.input) {
this.input.blur();
}
+
if (clear) {
this.setState({
more: {},
if (more[qualifier]) {
next.push('qualifier###' + qualifier);
}
+
return next;
}, []);
if (query.length === 0 || query.length >= MIN_SEARCH_QUERY_LENGTH) {
this.setState({ loading: true });
const recentlyBrowsed = RecentHistory.get().map((component) => component.key);
+
getSuggestions(query, recentlyBrowsed).then((response) => {
// compare `this.state.query` and `query` to handle two request done almost at the same time
// in this case only the request that matches the current query should be taken
searchMore = (qualifier: string) => {
const { query } = this.state;
+
if (query.length === 1) {
return;
}
this.setState({ loading: true, loadingMore: qualifier });
const recentlyBrowsed = RecentHistory.get().map((component) => component.key);
+
getSuggestions(query, recentlyBrowsed, qualifier).then((response) => {
if (this.mounted) {
const group = response.results.find((group) => group.q === qualifier);
const moreResults = (group ? group.items : []).map((item) => ({ ...item, qualifier }));
+
this.setState((state) => ({
loading: false,
loadingMore: undefined,
},
selected: moreResults.length > 0 ? moreResults[0].key : state.selected,
}));
+
this.focusInput();
}
}, this.stopLoading);
const index = list.indexOf(selected);
return index > 0 ? { selected: list[index - 1] } : null;
}
+
return null;
});
};
const index = list.indexOf(selected);
return index >= 0 && index < list.length - 1 ? { selected: list[index + 1] } : null;
}
+
return null;
});
};
}
if (selected.startsWith('qualifier###')) {
- this.searchMore(selected.substr(12));
+ this.searchMore(selected.substring('qualifier###'.length));
} else {
let qualifier = ComponentQualifier.Project;
const node = this.nodes[this.state.selected];
if (node && this.node) {
- scrollToElement(node, {
- topOffset: 30,
- bottomOffset: 60,
- parent: this.node,
- });
+ node.scrollIntoView();
}
}
};
if (!getKeyboardShortcutEnabled() || isInput(event) || isShortcut(event)) {
return true;
}
+
if (event.key === KeyboardKeys.KeyS) {
event.preventDefault();
this.focusInput();
render() {
const { open, query, results, more, loadingMore, selected, loading } = this.state;
+
if (!open && !query) {
return (
<Tooltip mouseEnterDelay={INTERACTIVE_TOOLTIP_DELAY} overlay={translate('search_verb')}>
}
const list = this.getPlainComponentsList(results, more);
+
const search = (
<div role="search" className="sw-min-w-abs-200 sw-max-w-abs-350 sw-w-full">
<PortalPopup
* 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';
* 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';
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>;
}
* 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';
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,
* 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';
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) {
* 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', () => ({
});
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 '
});
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(),
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(),
it('correctly treats result data', async () => {
const components = [mockComponent({ key: 'foo' }), mockComponent({ key: 'bar' })];
+
(searchProjects as jest.Mock).mockResolvedValue({
components,
facets: [
],
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,
});
});
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' });
+ });
+});
handleRequiredAuthentication();
return;
}
+
this.handleQueryChange();
+
addSideBarClass();
}
}
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,
};
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,
}
};
- 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 });
});
};
- handlePerspectiveChange = ({ view }: { view: string }) => {
+ handlePerspectiveChange = ({ view }: { view?: string }) => {
const query: {
view: string | undefined;
sort?: string | undefined;
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];
}
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;
}
it('fetches projects', () => {
shallowRender();
- expect(fetchProjects).toHaveBeenLastCalledWith(
- {
+
+ expect(fetchProjects).toHaveBeenLastCalledWith({
+ isFavorite: false,
+ query: {
coverage: undefined,
duplications: undefined,
gate: undefined,
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();
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: {} }}
{...props}
/>
);
+
wrapper.setState({
loading: false,
projects: [
],
total: 0,
});
+
return wrapper;
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`handles favorite projects 1`] = `
+exports[`handles showing favorite projects on load 1`] = `
[
{
"key": "foo",
]
`;
-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,
* 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';
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)])
componentMeasures[measure.metric] = value;
}
});
+
return { ...component, measures: componentMeasures };
}),
total: paging.total,
if (query.view === 'leak') {
return LEAK_METRICS;
}
+
return METRICS;
}
if (query.view === 'leak') {
return LEAK_FACETS;
}
+
return FACETS;
}
if (filter) {
data.filter = filter;
}
+
if (sort.s) {
data.s = sort.s;
}
+
if (sort.asc !== undefined) {
data.asc = sort.asc;
}
+
return data;
}
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;
}
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;
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;
}
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' },
* 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';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import classNames from 'classnames';
import { scaleLinear } from 'd3-scale';
import * as React from 'react';
</>
)}
- {this.props.label.substr(this.props.prefix.length)}
+ {this.props.label.substring(this.props.prefix.length)}
</div>
<div className="treemap-text-suffix little-spacer-top">{this.props.value}</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 classNames from 'classnames';
+import { isDarkColor } from 'design-system';
import * as React from 'react';
-import { isDarkColor } from '../../helpers/colors';
import { getBaseUrl } from '../../helpers/system';
import './IdentityProviderLink.css';
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
import classNames from 'classnames';
+import { getTextColor, stringToColor } from 'design-system';
import * as React from 'react';
-import { getTextColor, stringToColor } from '../../helpers/colors';
interface Props {
className?: string;
+++ /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 colors from '../colors';
-
-describe('#stringToColor', () => {
- it('should return a color for a text', () => {
- expect(colors.stringToColor('skywalker')).toBe('#97f047');
- });
-});
-
-describe('#isDarkColor', () => {
- it('should be dark', () => {
- expect(colors.isDarkColor('#000000')).toBe(true);
- expect(colors.isDarkColor('#222222')).toBe(true);
- expect(colors.isDarkColor('#000')).toBe(true);
- });
- it('should be light', () => {
- expect(colors.isDarkColor('#FFFFFF')).toBe(false);
- expect(colors.isDarkColor('#CDCDCD')).toBe(false);
- expect(colors.isDarkColor('#FFF')).toBe(false);
- });
-});
-
-describe('#getTextColor', () => {
- it('should return dark color', () => {
- expect(colors.getTextColor('#FFF', 'dark', 'light')).toBe('dark');
- expect(colors.getTextColor('#FFF')).toBe('#222');
- });
- it('should return light color', () => {
- expect(colors.getTextColor('#000', 'dark', 'light')).toBe('light');
- expect(colors.getTextColor('#000')).toBe('#fff');
- });
-});
+++ /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.
- */
-/* eslint-disable no-bitwise, no-mixed-operators */
-export function stringToColor(str: string) {
- let hash = 0;
- for (let i = 0; i < str.length; i++) {
- hash = str.charCodeAt(i) + ((hash << 5) - hash);
- }
- let color = '#';
- for (let i = 0; i < 3; i++) {
- const value = (hash >> (i * 8)) & 0xff;
- color += ('00' + value.toString(16)).substr(-2);
- }
- return color;
-}
-
-export function isDarkColor(color: string) {
- color = color.substr(1);
- if (color.length === 3) {
- // shortcut notation: #f90
- color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
- }
- const rgb = parseInt(color.substr(1), 16);
- const r = (rgb >> 16) & 0xff;
- const g = (rgb >> 8) & 0xff;
- const b = (rgb >> 0) & 0xff;
- const luma = 0.2126 * r + 0.7152 * g + 0.0722 * b;
- return luma < 140;
-}
-
-export function getTextColor(background: string, dark = '#222', light = '#fff') {
- return isDarkColor(background) ? light : dark;
-}