bluge.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { lastValueFrom } from 'rxjs';
  2. import { ArrayVector, DataFrame, DataFrameView, getDisplayProcessor, SelectableValue } from '@grafana/data';
  3. import { config, getDataSourceSrv } from '@grafana/runtime';
  4. import { TermCount } from 'app/core/components/TagFilter/TagFilter';
  5. import { GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource';
  6. import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
  7. import { DashboardQueryResult, GrafanaSearcher, QueryResponse, SearchQuery, SearchResultMeta } from '.';
  8. export class BlugeSearcher implements GrafanaSearcher {
  9. async search(query: SearchQuery): Promise<QueryResponse> {
  10. if (query.facet?.length) {
  11. throw 'facets not supported!';
  12. }
  13. return doSearchQuery(query);
  14. }
  15. async tags(query: SearchQuery): Promise<TermCount[]> {
  16. const ds = (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource;
  17. const target = {
  18. refId: 'TagsQuery',
  19. queryType: GrafanaQueryType.Search,
  20. search: {
  21. ...query,
  22. query: query.query ?? '*',
  23. sort: undefined, // no need to sort the initial query results (not used)
  24. facet: [{ field: 'tag' }],
  25. limit: 1, // 0 would be better, but is ignored by the backend
  26. },
  27. };
  28. const data = (
  29. await lastValueFrom(
  30. ds.query({
  31. targets: [target],
  32. } as any)
  33. )
  34. ).data as DataFrame[];
  35. for (const frame of data) {
  36. if (frame.fields[0].name === 'tag') {
  37. return getTermCountsFrom(frame);
  38. }
  39. }
  40. return [];
  41. }
  42. // This should eventually be filled by an API call, but hardcoded is a good start
  43. getSortOptions(): Promise<SelectableValue[]> {
  44. const opts: SelectableValue[] = [
  45. { value: 'name_sort', label: 'Alphabetically (A-Z)' },
  46. { value: '-name_sort', label: 'Alphabetically (Z-A)' },
  47. ];
  48. if (config.licenseInfo.enabledFeatures.analytics) {
  49. for (const sf of sortFields) {
  50. opts.push({ value: `-${sf.name}`, label: `${sf.display} (most)` });
  51. opts.push({ value: `${sf.name}`, label: `${sf.display} (least)` });
  52. }
  53. }
  54. return Promise.resolve(opts);
  55. }
  56. }
  57. const firstPageSize = 50;
  58. const nextPageSizes = 100;
  59. async function doSearchQuery(query: SearchQuery): Promise<QueryResponse> {
  60. const ds = (await getDataSourceSrv().get('-- Grafana --')) as GrafanaDatasource;
  61. const target = {
  62. refId: 'Search',
  63. queryType: GrafanaQueryType.Search,
  64. search: {
  65. ...query,
  66. query: query.query ?? '*',
  67. limit: firstPageSize,
  68. },
  69. };
  70. const rsp = await lastValueFrom(
  71. ds.query({
  72. targets: [target],
  73. } as any)
  74. );
  75. const first = (rsp.data?.[0] as DataFrame) ?? { fields: [], length: 0 };
  76. for (const field of first.fields) {
  77. field.display = getDisplayProcessor({ field, theme: config.theme2 });
  78. }
  79. // Make sure the object exists
  80. if (!first.meta?.custom) {
  81. first.meta = {
  82. ...first.meta,
  83. custom: {
  84. count: first.length,
  85. max_score: 1,
  86. },
  87. };
  88. }
  89. const meta = first.meta.custom as SearchResultMeta;
  90. if (!meta.locationInfo) {
  91. meta.locationInfo = {}; // always set it so we can append
  92. }
  93. // Set the field name to a better display name
  94. if (meta.sortBy?.length) {
  95. const field = first.fields.find((f) => f.name === meta.sortBy);
  96. if (field) {
  97. const name = getSortFieldDisplayName(field.name);
  98. meta.sortBy = name;
  99. field.name = name; // make it look nicer
  100. }
  101. }
  102. const view = new DataFrameView<DashboardQueryResult>(first);
  103. return {
  104. totalRows: meta.count ?? first.length,
  105. view,
  106. loadMoreItems: async (startIndex: number, stopIndex: number): Promise<void> => {
  107. console.log('LOAD NEXT PAGE', { startIndex, stopIndex, length: view.dataFrame.length });
  108. const from = view.dataFrame.length;
  109. const limit = stopIndex - from;
  110. if (limit < 0) {
  111. return;
  112. }
  113. const frame = (
  114. await lastValueFrom(
  115. ds.query({
  116. targets: [
  117. {
  118. ...target,
  119. search: {
  120. ...(target?.search ?? {}),
  121. from,
  122. limit: Math.max(limit, nextPageSizes),
  123. },
  124. refId: 'Page',
  125. facet: undefined,
  126. },
  127. ],
  128. } as any)
  129. )
  130. ).data?.[0] as DataFrame;
  131. if (!frame) {
  132. console.log('no results', frame);
  133. return;
  134. }
  135. if (frame.fields.length !== view.dataFrame.fields.length) {
  136. console.log('invalid shape', frame, view.dataFrame);
  137. return;
  138. }
  139. // Append the raw values to the same array buffer
  140. const length = frame.length + view.dataFrame.length;
  141. for (let i = 0; i < frame.fields.length; i++) {
  142. const values = (view.dataFrame.fields[i].values as ArrayVector).buffer;
  143. values.push(...frame.fields[i].values.toArray());
  144. }
  145. view.dataFrame.length = length;
  146. // Add all the location lookup info
  147. const submeta = frame.meta?.custom as SearchResultMeta;
  148. if (submeta?.locationInfo && meta) {
  149. for (const [key, value] of Object.entries(submeta.locationInfo)) {
  150. meta.locationInfo[key] = value;
  151. }
  152. }
  153. return;
  154. },
  155. isItemLoaded: (index: number): boolean => {
  156. return index < view.dataFrame.length;
  157. },
  158. };
  159. }
  160. function getTermCountsFrom(frame: DataFrame): TermCount[] {
  161. const keys = frame.fields[0].values;
  162. const vals = frame.fields[1].values;
  163. const counts: TermCount[] = [];
  164. for (let i = 0; i < frame.length; i++) {
  165. counts.push({ term: keys.get(i), count: vals.get(i) });
  166. }
  167. return counts;
  168. }
  169. // Enterprise only sort field values for dashboards
  170. const sortFields = [
  171. { name: 'views_total', display: 'Views total' },
  172. { name: 'views_last_30_days', display: 'Views 30 days' },
  173. { name: 'errors_total', display: 'Errors total' },
  174. { name: 'errors_last_30_days', display: 'Errors 30 days' },
  175. ];
  176. /** Given the internal field name, this gives a reasonable display name for the table colum header */
  177. function getSortFieldDisplayName(name: string) {
  178. for (const sf of sortFields) {
  179. if (sf.name === name) {
  180. return sf.display;
  181. }
  182. }
  183. return name;
  184. }