123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 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 { invert } from 'lodash';
- import { Facet, getScannableProjects, searchProjects } from '../../api/components';
- import { getMeasuresForProjects } from '../../api/measures';
- import { translate, translateWithParameters } from '../../helpers/l10n';
- import { isDiffMetric } from '../../helpers/measures';
- import { RequestData } from '../../helpers/request';
- import { MetricKey } from '../../types/metrics';
- import { Dict } from '../../types/types';
- import { Query, convertToFilter } from './query';
-
- interface SortingOption {
- class?: string;
- value: string;
- }
-
- export const PROJECTS_DEFAULT_FILTER = 'sonarqube.projects.default';
- export const PROJECTS_FAVORITE = 'favorite';
- export const PROJECTS_ALL = 'all';
-
- export const SORTING_METRICS: SortingOption[] = [
- { value: 'name' },
- { value: 'analysis_date' },
- { value: 'creation_date' },
- { value: 'reliability' },
- { value: 'security' },
- { value: 'security_review' },
- { value: 'maintainability' },
- { value: 'coverage' },
- { value: 'duplications' },
- { value: 'size' },
- ];
-
- export const SORTING_LEAK_METRICS: SortingOption[] = [
- { value: 'name' },
- { value: 'analysis_date' },
- { value: 'creation_date' },
- { value: 'new_reliability', class: 'projects-leak-sorting-option' },
- { value: 'new_security', class: 'projects-leak-sorting-option' },
- { value: 'new_security_review', class: 'projects-leak-sorting-option' },
- { value: 'new_maintainability', class: 'projects-leak-sorting-option' },
- { value: 'new_coverage', class: 'projects-leak-sorting-option' },
- { value: 'new_duplications', class: 'projects-leak-sorting-option' },
- { value: 'new_lines', class: 'projects-leak-sorting-option' },
- ];
-
- export const SORTING_SWITCH: Dict<string> = {
- analysis_date: 'analysis_date',
- name: 'name',
- reliability: 'new_reliability',
- security: 'new_security',
- security_review: 'new_security_review',
- maintainability: 'new_maintainability',
- coverage: 'new_coverage',
- duplications: 'new_duplications',
- size: 'new_lines',
- new_reliability: 'reliability',
- new_security: 'security',
- new_security_review: 'security_review',
- new_maintainability: 'maintainability',
- new_coverage: 'coverage',
- new_duplications: 'duplications',
- new_lines: 'size',
- };
-
- export const VIEWS = [
- { value: 'overall', label: 'overall' },
- { value: 'leak', label: 'new_code' },
- ];
-
- const PAGE_SIZE = 50;
-
- export const METRICS = [
- MetricKey.alert_status,
- MetricKey.reliability_issues,
- MetricKey.bugs,
- MetricKey.reliability_rating,
- MetricKey.security_issues,
- MetricKey.vulnerabilities,
- MetricKey.security_rating,
- MetricKey.maintainability_issues,
- MetricKey.code_smells,
- MetricKey.sqale_rating,
- MetricKey.security_hotspots_reviewed,
- MetricKey.security_review_rating,
- MetricKey.duplicated_lines_density,
- MetricKey.coverage,
- MetricKey.ncloc,
- MetricKey.ncloc_language_distribution,
- MetricKey.projects,
- ];
-
- export const LEAK_METRICS = [
- MetricKey.alert_status,
- MetricKey.new_violations,
- MetricKey.new_security_hotspots_reviewed,
- MetricKey.new_security_review_rating,
- MetricKey.new_coverage,
- MetricKey.new_duplicated_lines_density,
- MetricKey.new_lines,
- MetricKey.projects,
- ];
-
- export const FACETS = [
- 'reliability_rating',
- 'security_rating',
- 'security_review_rating',
- 'sqale_rating',
- 'coverage',
- 'duplicated_lines_density',
- 'ncloc',
- 'alert_status',
- 'languages',
- 'tags',
- 'qualifier',
- ];
-
- export const LEAK_FACETS = [
- 'new_reliability_rating',
- 'new_security_rating',
- 'new_security_review_rating',
- 'new_maintainability_rating',
- 'new_coverage',
- 'new_duplicated_lines_density',
- 'new_lines',
- 'alert_status',
- 'languages',
- 'tags',
- 'qualifier',
- ];
-
- const REVERSED_FACETS = ['coverage', 'new_coverage'];
- let scannableProjectsCached: { key: string; name: string }[] | null = null;
-
- export function localizeSorting(sort?: string): string {
- return translate('projects.sort', sort ?? 'name');
- }
-
- export function parseSorting(sort: string): { sortValue: string; sortDesc: boolean } {
- const desc = sort.startsWith('-');
-
- return { sortValue: desc ? sort.substring(1) : sort, sortDesc: desc };
- }
-
- export async function fetchScannableProjects() {
- if (scannableProjectsCached) {
- return Promise.resolve({ scannableProjects: scannableProjectsCached });
- }
-
- const response = await getScannableProjects().then(({ projects }) => {
- scannableProjectsCached = projects;
- return projects;
- });
-
- return { scannableProjects: response };
- }
-
- 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),
- fetchScannableProjects(),
- ]),
- )
- .then(([measures, { components, facets, paging }, { scannableProjects }]) => {
- return {
- facets: getFacetsMap(facets),
- projects: components.map((component) => {
- const componentMeasures: Dict<string> = {};
- measures
- .filter((measure) => measure.component === component.key)
- .forEach((measure) => {
- const value = isDiffMetric(measure.metric) ? measure.period?.value : measure.value;
- if (value !== undefined) {
- componentMeasures[measure.metric] = value;
- }
- });
-
- return {
- ...component,
- measures: componentMeasures,
- isScannable: scannableProjects.find((p) => p.key === component.key) !== undefined,
- };
- }),
- total: paging.total,
- };
- });
- }
-
- export function defineMetrics(query: Query): string[] {
- if (query.view === 'leak') {
- return LEAK_METRICS;
- }
-
- return METRICS;
- }
-
- function defineFacets(query: Query): string[] {
- if (query.view === 'leak') {
- return LEAK_FACETS;
- }
-
- return FACETS;
- }
-
- export function convertToQueryData(query: Query, isFavorite: boolean, defaultData = {}) {
- const data: RequestData = { ...defaultData };
- const filter = convertToFilter(query, isFavorite);
- const sort = convertToSorting(query);
-
- if (filter) {
- data.filter = filter;
- }
-
- if (sort.s) {
- data.s = sort.s;
- }
-
- if (sort.asc !== undefined) {
- data.asc = sort.asc;
- }
-
- return data;
- }
-
- export function fetchProjectMeasures(projects: Array<{ key: string }>, query: Query) {
- if (!projects.length) {
- return Promise.resolve([]);
- }
-
- 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;
- }
-
- const propertyToMetricMap: Dict<string | undefined> = {
- analysis_date: 'analysisDate',
- reliability: 'reliability_rating',
- new_reliability: 'new_reliability_rating',
- security: 'security_rating',
- new_security: 'new_security_rating',
- security_review: 'security_review_rating',
- new_security_review: 'new_security_review_rating',
- maintainability: 'sqale_rating',
- new_maintainability: 'new_maintainability_rating',
- coverage: 'coverage',
- new_coverage: 'new_coverage',
- duplications: 'duplicated_lines_density',
- new_duplications: 'new_duplicated_lines_density',
- size: 'ncloc',
- new_lines: 'new_lines',
- gate: 'alert_status',
- languages: 'languages',
- tags: 'tags',
- search: 'query',
- qualifier: 'qualifier',
- creation_date: 'creationDate',
- };
-
- 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;
- }
-
- export function convertToSorting({ sort }: Query): { s?: string; asc?: boolean } {
- if (sort?.startsWith('-')) {
- return { s: propertyToMetricMap[sort.substring(1)], asc: false };
- }
-
- return { s: propertyToMetricMap[sort ?? ''] };
- }
-
- const ONE_MINUTE = 60000;
- const ONE_HOUR = 60 * ONE_MINUTE;
- const ONE_DAY = 24 * ONE_HOUR;
- const ONE_MONTH = 30 * ONE_DAY;
- const ONE_YEAR = 12 * ONE_MONTH;
-
- 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;
- }
-
- 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' },
- { value: days, label: 'duration.days' },
- { value: hours, label: 'duration.hours' },
- { value: minutes, label: 'duration.minutes' },
- ]);
- }
|