formatMongoDBQuery.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import type {ReactElement} from 'react';
  2. import * as Sentry from '@sentry/react';
  3. import {jsonrepair} from 'jsonrepair';
  4. type JsonPrimitive = string | number | boolean | null;
  5. type JSONValue = JsonPrimitive | Record<string, JsonPrimitive>;
  6. /**
  7. * Takes in a MongoDB query JSON string and outputs it as HTML tokens.
  8. * Performs some processing to surface the DB operation and collection so they are the first key-value
  9. * pair in the query, and **bolds** the operation
  10. *
  11. * @param query The query as a JSON string
  12. * @param command The DB command, e.g. `find`. This is available as a tag on database spans
  13. */
  14. export function formatMongoDBQuery(query: string, command: string) {
  15. const sentrySpan = Sentry.startInactiveSpan({
  16. op: 'function',
  17. name: 'formatMongoDBQuery',
  18. attributes: {
  19. query,
  20. command,
  21. },
  22. onlyIfParent: true,
  23. });
  24. let queryObject: Record<string, JSONValue> = {};
  25. try {
  26. queryObject = JSON.parse(query);
  27. } catch {
  28. try {
  29. const repairedJson = jsonrepair(query);
  30. queryObject = JSON.parse(repairedJson);
  31. } catch {
  32. return query;
  33. }
  34. }
  35. const tokens: ReactElement[] = [];
  36. const tempTokens: ReactElement[] = [];
  37. const queryEntries = Object.entries(queryObject);
  38. queryEntries.forEach(([key, val]) => {
  39. const isBoldedEntry = key.toLowerCase() === command.toLowerCase();
  40. // Push the bolded entry into tokens so it is the first entry displayed.
  41. // The other tokens will be pushed into tempTokens, and then copied into tokens afterwards
  42. if (isBoldedEntry) {
  43. tokens.push(jsonEntryToToken(key, val, true));
  44. } else {
  45. tempTokens.push(jsonEntryToToken(key, val));
  46. }
  47. });
  48. if (tokens.length === 1 && tempTokens.length > 0) {
  49. tokens.push(stringToToken(', ', `${tokens[0]!.key}:,`));
  50. }
  51. tempTokens.forEach((token, index) => {
  52. tokens.push(token);
  53. if (index !== tempTokens.length - 1) {
  54. tokens.push(stringToToken(', ', `${token.key}:${index}`));
  55. }
  56. });
  57. sentrySpan.end();
  58. return tokens;
  59. }
  60. function jsonEntryToToken(key: string, value: JSONValue, isBold?: boolean) {
  61. const tokenString = jsonToTokenizedString(value, key);
  62. return stringToToken(tokenString, `${key}:${value}`, isBold);
  63. }
  64. function jsonToTokenizedString(value: JSONValue | JSONValue[], key?: string): string {
  65. let result = '';
  66. if (key) {
  67. result = `"${key}": `;
  68. }
  69. // Case 1: Value is null
  70. if (!value) {
  71. result += 'null';
  72. return result;
  73. }
  74. // Case 2: Value is a string
  75. if (typeof value === 'string') {
  76. result += `"${value}"`;
  77. return result;
  78. }
  79. // Case 3: Value is one of the other primitive types
  80. if (typeof value === 'number' || typeof value === 'boolean') {
  81. result += `${value}`;
  82. return result;
  83. }
  84. // Case 4: Value is an array
  85. if (Array.isArray(value)) {
  86. result += '[';
  87. value.forEach((item, index) => {
  88. if (index === value.length - 1) {
  89. result += jsonToTokenizedString(item);
  90. } else {
  91. result += `${jsonToTokenizedString(item)}, `;
  92. }
  93. });
  94. result += ']';
  95. return result;
  96. }
  97. // Case 5: Value is an object
  98. if (typeof value === 'object') {
  99. const entries = Object.entries(value);
  100. if (entries.length === 0) {
  101. result += '{}';
  102. return result;
  103. }
  104. result += '{ ';
  105. entries.forEach(([_key, val], index) => {
  106. if (index === entries.length - 1) {
  107. result += jsonToTokenizedString(val, _key);
  108. } else {
  109. result += `${jsonToTokenizedString(val, _key)}, `;
  110. }
  111. });
  112. result += ' }';
  113. return result;
  114. }
  115. // This branch should never be reached
  116. return '';
  117. }
  118. function stringToToken(str: string, keyProp: string, isBold?: boolean): ReactElement {
  119. return isBold ? <b key={keyProp}>{str}</b> : <span key={keyProp}>{str}</span>;
  120. }