helpers.js 5.71 KB
Newer Older
1
import isPlainObject from 'lodash/isPlainObject';
2
import { REPORT_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
3
import { BASE_FILTERS } from 'ee/security_dashboard/store/modules/filters/constants';
4
import convertReportType from 'ee/vue_shared/security_reports/store/utils/convert_report_type';
5
import { VULNERABILITY_STATES } from 'ee/vulnerabilities/constants';
6
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
7
import { convertObjectPropsToSnakeCase } from '~/lib/utils/common_utils';
8
import { s__, __ } from '~/locale';
9
import { DEFAULT_SCANNER } from './constants';
Savas Vedova's avatar
Savas Vedova committed
10

11
const parseOptions = (obj) =>
Savas Vedova's avatar
Savas Vedova committed
12 13
  Object.entries(obj).map(([id, name]) => ({ id: id.toUpperCase(), name }));

14 15
export const mapProjects = (projects = []) =>
  projects.map((p) => ({ id: getIdFromGraphQLId(p.id).toString(), name: p.name }));
Savas Vedova's avatar
Savas Vedova committed
16

17
const stateOptions = parseOptions(VULNERABILITY_STATES);
18
const defaultStateOptions = stateOptions.filter((x) => ['DETECTED', 'CONFIRMED'].includes(x.id));
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

export const stateFilter = {
  name: s__('SecurityReports|Status'),
  id: 'state',
  options: stateOptions,
  allOption: BASE_FILTERS.state,
  defaultOptions: defaultStateOptions,
};

export const severityFilter = {
  name: s__('SecurityReports|Severity'),
  id: 'severity',
  options: parseOptions(SEVERITY_LEVELS),
  allOption: BASE_FILTERS.severity,
  defaultOptions: [],
};

36 37 38 39
export const createScannerOption = (vendor, reportType) => {
  const type = reportType.toUpperCase();

  return {
40
    id: `${vendor}.${type}`,
41 42
    reportType: reportType.toUpperCase(),
    name: convertReportType(reportType),
43
    scannerIds: [],
44 45 46
  };
};

47 48 49
// This is used on the pipeline security tab, group-level report, and instance-level report. It's
// used by the scanner filter that shows a flat list of scan types (DAST, SAST, etc) with no vendor
// grouping.
50
export const simpleScannerFilter = {
51 52 53 54 55 56 57
  name: s__('SecurityReports|Scanner'),
  id: 'reportType',
  options: parseOptions(REPORT_TYPES),
  allOption: BASE_FILTERS.report_type,
  defaultOptions: [],
};

58 59 60
// This is used on the project-level report. It's used by the scanner filter that shows a list of
// scan types (DAST, SAST, etc) that's grouped by vendor.
export const vendorScannerFilter = {
61
  name: s__('SecurityReports|Scanner'),
62
  id: 'scanner',
63
  options: Object.keys(REPORT_TYPES).map((x) => createScannerOption(DEFAULT_SCANNER, x)),
64 65 66 67
  allOption: BASE_FILTERS.report_type,
  defaultOptions: [],
};

68 69 70 71 72 73 74 75 76 77 78 79 80 81
export const activityOptions = {
  NO_ACTIVITY: { id: 'NO_ACTIVITY', name: s__('SecurityReports|No activity') },
  WITH_ISSUES: { id: 'WITH_ISSUES', name: s__('SecurityReports|With issues') },
  NO_LONGER_DETECTED: { id: 'NO_LONGER_DETECTED', name: s__('SecurityReports|No longer detected') },
};

export const activityFilter = {
  name: s__('Reports|Activity'),
  id: 'activity',
  options: Object.values(activityOptions),
  allOption: BASE_FILTERS.activity,
  defaultOptions: [],
};

82
export const getProjectFilter = (projects) => {
83 84 85 86 87 88 89
  return {
    name: s__('SecurityReports|Project'),
    id: 'projectId',
    options: mapProjects(projects),
    allOption: BASE_FILTERS.project_id,
    defaultOptions: [],
  };
Savas Vedova's avatar
Savas Vedova committed
90 91
};

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
/**
 * Provided a security reports summary from the GraphQL API, this returns an array of arrays
 * representing a properly formatted report ready to be displayed in the UI. Each sub-array consists
 * of the user-friend report's name, and the summary's payload. Note that summary entries are
 * considered empty and are filtered out of the return if the payload is `null` or don't include
 * a vulnerabilitiesCount property. Report types whose name can't be matched to a user-friendly
 * name are filtered out as well.
 *
 * Take the following summary for example:
 * {
 *   containerScanning: { vulnerabilitiesCount: 123 },
 *   invalidReportType: { vulnerabilitiesCount: 123 },
 *   dast: null,
 * }
 *
 * The formatted summary would look like this:
 * [
 *   ['containerScanning', { vulnerabilitiesCount: 123 }]
 * ]
 *
 * Note that `invalidReportType` was filtered out as it can't be matched with a user-friendly name,
 * and the DAST report was omitted because it's empty (`null`).
 *
 * @param {Object} rawSummary
 * @returns {Array}
 */
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
export const getFormattedSummary = (rawSummary = {}) => {
  if (!isPlainObject(rawSummary)) {
    return [];
  }
  // Convert keys to snake case so they can be matched against REPORT_TYPES keys for translation
  const snakeCasedSummary = convertObjectPropsToSnakeCase(rawSummary);
  // Convert object to an array of entries to make it easier to loop through
  const summaryEntries = Object.entries(snakeCasedSummary);
  // Filter out empty entries as we don't want to display those in the summary
  const withoutEmptyEntries = summaryEntries.filter(
    ([, scanSummary]) => scanSummary?.vulnerabilitiesCount !== undefined,
  );
  // Replace keys with translations found in REPORT_TYPES if available
  const formattedEntries = withoutEmptyEntries.map(([scanType, scanSummary]) => {
    const name = REPORT_TYPES[scanType];
    return name ? [name, scanSummary] : null;
  });
  // Filter out keys that could not be matched with any translation and are thus considered invalid
136
  return formattedEntries.filter((entry) => entry !== null);
137 138
};

139 140 141 142 143 144 145 146
/**
 * We have disabled loading hasNextPage from GraphQL as it causes timeouts in database,
 * instead we have to calculate that value based on the existence of endCursor. When endCursor
 * is empty or has null value, that means that there is no next page to be loaded from GraphQL API.
 *
 * @param {Object} pageInfo
 * @returns {Object}
 */
147
export const preparePageInfo = (pageInfo) => {
148 149 150
  return { ...pageInfo, hasNextPage: Boolean(pageInfo?.endCursor) };
};

151
export const PROJECT_LOADING_ERROR_MESSAGE = __('An error occurred while retrieving projects.');
152

Savas Vedova's avatar
Savas Vedova committed
153
export default () => ({});