links.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { useCallback } from 'react';
  2. import {
  3. Field,
  4. LinkModel,
  5. TimeRange,
  6. mapInternalLinkToExplore,
  7. InterpolateFunction,
  8. ScopedVars,
  9. DataFrame,
  10. getFieldDisplayValuesProxy,
  11. SplitOpen,
  12. } from '@grafana/data';
  13. import { getTemplateSrv } from '@grafana/runtime';
  14. import { contextSrv } from 'app/core/services/context_srv';
  15. import { getLinkSrv } from '../../panel/panellinks/link_srv';
  16. /**
  17. * Get links from the field of a dataframe and in addition check if there is associated
  18. * metadata with datasource in which case we will add onClick to open the link in new split window. This assumes
  19. * that we just supply datasource name and field value and Explore split window will know how to render that
  20. * appropriately. This is for example used for transition from log with traceId to trace datasource to show that
  21. * trace.
  22. */
  23. export const getFieldLinksForExplore = (options: {
  24. field: Field;
  25. rowIndex: number;
  26. splitOpenFn?: SplitOpen;
  27. range: TimeRange;
  28. vars?: ScopedVars;
  29. dataFrame?: DataFrame;
  30. }): Array<LinkModel<Field>> => {
  31. const { field, vars, splitOpenFn, range, rowIndex, dataFrame } = options;
  32. const scopedVars: any = { ...(vars || {}) };
  33. scopedVars['__value'] = {
  34. value: {
  35. raw: field.values.get(rowIndex),
  36. },
  37. text: 'Raw value',
  38. };
  39. // If we have a dataFrame we can allow referencing other columns and their values in the interpolation.
  40. if (dataFrame) {
  41. scopedVars['__data'] = {
  42. value: {
  43. name: dataFrame.name,
  44. refId: dataFrame.refId,
  45. fields: getFieldDisplayValuesProxy({
  46. frame: dataFrame,
  47. rowIndex,
  48. }),
  49. },
  50. text: 'Data',
  51. };
  52. }
  53. if (field.config.links) {
  54. const links = [];
  55. if (!contextSrv.hasAccessToExplore()) {
  56. links.push(...field.config.links.filter((l) => !l.internal));
  57. } else {
  58. links.push(...field.config.links);
  59. }
  60. return links.map((link) => {
  61. if (!link.internal) {
  62. const replace: InterpolateFunction = (value, vars) =>
  63. getTemplateSrv().replace(value, { ...vars, ...scopedVars });
  64. const linkModel = getLinkSrv().getDataLinkUIModel(link, replace, field);
  65. if (!linkModel.title) {
  66. linkModel.title = getTitleFromHref(linkModel.href);
  67. }
  68. return linkModel;
  69. } else {
  70. return mapInternalLinkToExplore({
  71. link,
  72. internalLink: link.internal,
  73. scopedVars: scopedVars,
  74. range,
  75. field,
  76. onClickFn: splitOpenFn,
  77. replaceVariables: getTemplateSrv().replace.bind(getTemplateSrv()),
  78. });
  79. }
  80. });
  81. }
  82. return [];
  83. };
  84. function getTitleFromHref(href: string): string {
  85. // The URL constructor needs the url to have protocol
  86. if (href.indexOf('://') < 0) {
  87. // Doesn't really matter what protocol we use.
  88. href = `http://${href}`;
  89. }
  90. let title;
  91. try {
  92. const parsedUrl = new URL(href);
  93. title = parsedUrl.hostname;
  94. } catch (_e) {
  95. // Should be good enough fallback, user probably did not input valid url.
  96. title = href;
  97. }
  98. return title;
  99. }
  100. /**
  101. * Hook that returns a function that can be used to retrieve all the links for a row. This returns all the links from
  102. * all the fields so is useful for visualisation where the whole row is represented as single clickable item like a
  103. * service map.
  104. */
  105. export function useLinks(range: TimeRange, splitOpenFn?: SplitOpen) {
  106. return useCallback(
  107. (dataFrame: DataFrame, rowIndex: number) => {
  108. return dataFrame.fields.flatMap((f) => {
  109. if (f.config?.links && f.config?.links.length) {
  110. return getFieldLinksForExplore({
  111. field: f,
  112. rowIndex: rowIndex,
  113. range,
  114. dataFrame,
  115. splitOpenFn,
  116. });
  117. } else {
  118. return [];
  119. }
  120. });
  121. },
  122. [range, splitOpenFn]
  123. );
  124. }