utils.tsx 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import isEqual from 'lodash/isEqual';
  2. import {
  3. aggregateOutputType,
  4. isAggregateField,
  5. isLegalYAxisType,
  6. } from 'app/utils/discover/fields';
  7. import {Widget} from 'app/views/dashboardsV2/types';
  8. import {DisplayType} from '../utils';
  9. type ValidationError = {
  10. [key: string]: string[] | ValidationError[] | ValidationError;
  11. };
  12. type FlatValidationError = {
  13. [key: string]: string | FlatValidationError[] | FlatValidationError;
  14. };
  15. export function mapErrors(
  16. data: ValidationError,
  17. update: FlatValidationError
  18. ): FlatValidationError {
  19. Object.keys(data).forEach((key: string) => {
  20. const value = data[key];
  21. // Recurse into nested objects.
  22. if (Array.isArray(value) && typeof value[0] === 'string') {
  23. update[key] = value[0];
  24. return;
  25. } else if (Array.isArray(value) && typeof value[0] === 'object') {
  26. update[key] = (value as ValidationError[]).map(item => mapErrors(item, {}));
  27. } else {
  28. update[key] = mapErrors(value as ValidationError, {});
  29. }
  30. });
  31. return update;
  32. }
  33. export function normalizeQueries(
  34. displayType: DisplayType,
  35. queries: Widget['queries']
  36. ): Widget['queries'] {
  37. const isTimeseriesChart = [
  38. DisplayType.LINE,
  39. DisplayType.AREA,
  40. DisplayType.STACKED_AREA,
  41. DisplayType.BAR,
  42. ].includes(displayType);
  43. if (
  44. [DisplayType.TABLE, DisplayType.WORLD_MAP, DisplayType.BIG_NUMBER].includes(
  45. displayType
  46. )
  47. ) {
  48. // Some display types may only support at most 1 query.
  49. queries = queries.slice(0, 1);
  50. } else if (isTimeseriesChart) {
  51. // Timeseries charts supports at most 3 queries.
  52. queries = queries.slice(0, 3);
  53. }
  54. if (displayType === DisplayType.TABLE) {
  55. return queries;
  56. }
  57. // Filter out non-aggregate fields
  58. queries = queries.map(query => {
  59. let fields = query.fields.filter(isAggregateField);
  60. if (isTimeseriesChart || displayType === DisplayType.WORLD_MAP) {
  61. // Filter out fields that will not generate numeric output types
  62. fields = fields.filter(field => isLegalYAxisType(aggregateOutputType(field)));
  63. }
  64. if (isTimeseriesChart && fields.length && fields.length > 3) {
  65. // Timeseries charts supports at most 3 fields.
  66. fields = fields.slice(0, 3);
  67. }
  68. return {
  69. ...query,
  70. fields: fields.length ? fields : ['count()'],
  71. };
  72. });
  73. if (isTimeseriesChart) {
  74. // For timeseries widget, all queries must share identical set of fields.
  75. const referenceFields = [...queries[0].fields];
  76. queryLoop: for (const query of queries) {
  77. if (referenceFields.length >= 3) {
  78. break;
  79. }
  80. if (isEqual(referenceFields, query.fields)) {
  81. continue;
  82. }
  83. for (const field of query.fields) {
  84. if (referenceFields.length >= 3) {
  85. break queryLoop;
  86. }
  87. if (!referenceFields.includes(field)) {
  88. referenceFields.push(field);
  89. }
  90. }
  91. }
  92. queries = queries.map(query => {
  93. return {
  94. ...query,
  95. fields: referenceFields,
  96. };
  97. });
  98. }
  99. if ([DisplayType.WORLD_MAP, DisplayType.BIG_NUMBER].includes(displayType)) {
  100. // For world map chart, cap fields of the queries to only one field.
  101. queries = queries.map(query => {
  102. return {
  103. ...query,
  104. fields: query.fields.slice(0, 1),
  105. };
  106. });
  107. }
  108. return queries;
  109. }